Newer
Older
/*!
* Piwik - Web Analytics
*
* Visitors Map with zoom in continents / countries. Cities + Region view.
* Using Kartograph.js http://kartograph.org/
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
// create a global namespace for UserCountryMap plugin
// this is used both by visitor map and realtime map
window.UserCountryMap = window.UserCountryMap || {};
// the main class for this widget, provides the interface for the template
var VisitorMap = window.UserCountryMap.VisitorMap = function (config, theWidget) {
this.theWidget = theWidget || false;
this.run();
};
$.extend(VisitorMap.prototype, {
run: function () {
var self = this,
config = self.config;
/*
* our own custom selector to only select stuff of this widget
*/
function $$(selector) {
return $(selector, self.theWidget ? self.theWidget.element : undefined);
var mapContainer = $$('.UserCountryMap_map').get(0),
map = self.map = Kartograph.map(mapContainer),
main = $$('.UserCountryMap_container'),
worldTotalVisits = 0,
width = main.width(),
_ = config._;
config.noDataColor = '#E4E2D7';
self.widget = $$('.widgetUserCountryMapvisitorMap').parent();
//window.__mapInstances = window.__mapInstances || [];
//window.__mapInstances.push(map);
function _reportParams(module, action, countryFilter) {
var params = $.extend(config.reqParams, {
module: 'API',
method: 'API.getProcessedReport',
apiModule: module,
apiAction: action,
filter_limit: -1,
limit: -1
});
if (countryFilter) {
$.extend(params, {
filter_column: 'country',
filter_sort_column: 'nb_visits',
filter_pattern: countryFilter
});
}
return params;
/*
* wrapper around jQuery.ajax, moves token_auth parameter
* to POST data while keeping other parameters as GET
*/
function ajax(params, dataType) {
dataType = dataType || 'json';
params = $.extend({}, params);
var token_auth = '' + params.token_auth;
delete params['token_auth'];
return $.ajax({
url: 'index.php?' + $.param(params),
dataType: dataType,
data: { token_auth: token_auth },
type: 'POST'
});
}
values = values.sort(function (a, b) { return Number(a) - Number(b); });
max: values[values.length - 1],
median: values[Math.floor(values.length * 0.5)],
p33: values[Math.floor(values.length * 0.33)],
p66: values[Math.floor(values.length * 0.66)],
p90: values[Math.floor(values.length * 0.9)]
function formatNumber(v) {
v = Number(v);
return v > 1000000 ? (v / 1000000).toFixed(1) + 'm' :
v > 1000 ? (v / 1000).toFixed(1) + 'k' :
v;
//
// Since some metrics are transmitted in an non-numeric format like
// "61.45%", we need to parse the numbers to make sure they can be
// used for color scales etc. The parsed metrics will be stored as
// METRIC_raw
//
function formatValueForTooltips(data, metric, id) {
var val = data[metric] % 1 === 0 || Number(data[metric]) != data[metric] ? data[metric] : data[metric].toFixed(1),
v = _[metric].replace('%s', '<strong>' + val + '</strong>');
function avgTime(d) { return d['sum_visit_length'] / d['nb_visits']; }
if (metric.substr(0, 3) == 'nb_' && metric != 'nb_actions_per_visit') {
var total;
if (id.length == 3) total = UserCountryMap.countriesByIso[id][metric];
else if (id == 'world') total = _worldTotal;
else {
total = 0;
$.each(UserCountryMap.countriesByIso, function (iso, country) {
if (UserCountryMap.ISO3toCONT[iso] == id) {
total += country[metric];
}
});
}
if (total) {
v += ' (' + formatPercentage(data[metric] / total) + ')';
}
} else if (metric == 'avg_time_on_site') {
v += '<br/> (' + _.nb_visits.replace('%s', data.nb_visits) + ')';
function getColorScale(rows, metric, filter, choropleth) {
var colscale;
function addLegendItem(val, first) {
var d = $('<div>'), r = $('<div>'), l = $('<div>'),
metric = $$('.userCountryMapSelectMetrics').val(),
v = formatNumber(Math.round(val)) + (metric == 'avg_time_on_site' ? first ? ' sec' : 's' : '');
d.css({ width: 17, height: 17, float: 'left', background: colscale(val) });
l.css({ 'margin-left': 20, 'line-height': '20px', 'text-align': 'right' }).html(v);
r.css({ clear: 'both', height: 19 });
r.append(d).append(l);
$('.UserCountryMap-legend .content').append(r);
}
var stats, values = [], id = self.lastSelected, c;
$.each(rows, function (i, r) {
if (!$.isFunction(filter) || filter(r)) {
var v = quantify(r, metric);
if (!isNaN(v)) values.push(v);
}
colscale = function () { return chroma.hex('#CDDAEF'); };
if (choropleth) {
$('.UserCountryMap-legend .content').html('').show();
addLegendItem(stats.min, true);
}
return colscale;
}
colscale = chroma.scale()
.range([choropleth ? '#CDDAEF' : '#385993', '#385993'])
.domain(values, 4, 'c')
.mode('lch');
if (metric == 'avg_time_on_site' || metric == 'nb_actions_per_visit' || metric == 'bounce_rate') {
if (id.length == 3) {
c = (stats.p90 - stats.min) / (stats.max - stats.min);
colscale = chroma.scale(['#385993', '#385993', '#E87500', '#E87500'], [0, c, c + 0.001, 1])
.domain(chroma.limits(rows, 'c', 5, 'curMetric', filter))
.mode('hsl');
// a good place to update the legend, isn't it?
if (choropleth) {
$('.UserCountryMap-legend .content').html('').show();
var itemExists = {};
$.each(chroma.limits(values, 'k', 3), function (i, v) {
if (itemExists[v]) return;
addLegendItem(v, i === 0);
itemExists[v] = true;
});
} else {
$('.UserCountryMap-legend .content').hide();
function formatPercentage(val) {
if (val < 0.001) return '< 0.1%';
return Math.round(1000 * val) / 10 + '%';
/*
* to ensure that onResize is not called a hundred times
* while resizing the browser window, this functions
* makes sure to only call onResize at the end
*/
function onResizeLazy() {
clearTimeout(self._resizeTimer);
self._resizeTimer = setTimeout(self.resize.bind(self), 300);
function activateButton(btn) {
$$('.UserCountryMap-view-mode-buttons a').removeClass('activeIcon');
btn.addClass('activeIcon');
$$('.UserCountryMap-activeItem').offset({ left: btn.offset().left });
}
function initUserInterface() {
// react to changes of country select
$$('.userCountryMapSelectCountry').off('change').change(function () {
updateState($$('.userCountryMapSelectCountry').val());
});
function zoomOut() {
var t = self.lastSelected,
tgt = 'world'; // zoom out to world per default..
if (t.length == 3 && UserCountryMap.ISO3toCONT[t] !== undefined) {
tgt = UserCountryMap.ISO3toCONT[t]; // ..but zoom to continent if we know it
}
updateState(tgt);
}
// enable zoom-out
$$('.UserCountryMap-btn-zoom').off('click').click(zoomOut);
$$('.UserCountryMap_map').off('click').click(zoomOut);
// handle window resizes
$(window).off('resize').resize(onResizeLazy);
$$('.userCountryMapSelectMetrics').off('change').change(function () {
updateState(self.lastSelected);
(function (btn) {
btn.off('click').click(function () {
if (self.lastSelected.length == 3) {
if (self.mode != "city") {
self.mode = "city";
updateState(self.lastSelected);
}
}
});
})($$('.UserCountryMap-btn-city'));
// handle region button
(function (btn) {
btn.off('click').click(function () {
if (self.mode != "region") {
$$('.UserCountryMap-view-mode-buttons a').removeClass('activeIcon');
self.mode = "region";
updateState(self.lastSelected);
}
});
})($$('.UserCountryMap-btn-region'));
// add loading indicator overlay
var bl = $('<div id="UserCountryMap-black"></div>');
bl.hide();
$$('.UserCountryMap_map').append(bl);
var infobtn = $('.UserCountryMap-info-btn');
infobtn.off('mouseenter').on('mouseenter',function (e) {
$(infobtn.data('tooltip-target')).show();
}).off('mouseleave').on('mouseleave', function (e) {
$(infobtn.data('tooltip-target')).hide();
});
$('.UserCountryMap-tooltip').hide();
/*
* updateState, called whenever the view changes
*/
function updateState(id) {
// double check view mode
if (self.mode == "city" && id.length != 3) {
// city mode is reserved for country views
self.mode = "region";
var metric = $$('.userCountryMapSelectMetrics').val();
// store current map state
self.widget.dashboardWidget('setParameters', {
lastMap: id, viewMode: self.mode, lastMetric: metric
});
$('.UserCountryMap-info-btn').hide();
try {
// check which map to render
if (id.length == 3) {
// country map
renderCountryMap(id, metric);
} else {
// world or continent map
renderWorldMap(id, metric);
}
} catch (e) {
// console.error(e);
$('.UserCountryMap-info .content').html(e);
$('.UserCountryMap-info').show();
_updateUI(id, metric);
self.lastSelected = id;
/*
* update the widgets ui according to the currently selected view
*/
function _updateUI(id, metric) {
// update UI
if (self.mode == "city") {
activateButton($$('.UserCountryMap-btn-city'));
} else {
activateButton($$('.UserCountryMap-btn-region'));
}
var countrySelect = $$('.userCountryMapSelectCountry');
countrySelect.val(id);
var zoom = $$('.UserCountryMap-btn-zoom');
if (id == 'world') zoom.addClass('inactiveIcon');
else zoom.removeClass('inactiveIcon');
// show flag icon in select box
var flag = $$('.userCountryMapFlag'),
regionBtn = $$('.UserCountryMap-btn-region');
if (id.length == 3) {
if (UserCountryMap.countriesByIso[id]) { // we have visits in this country
flag.css({
'background-image': 'url(' + UserCountryMap.countriesByIso[id].flag + ')',
'background-repeat': 'no-repeat',
'background-position': '5px 5px'
});
$$('.UserCountryMap-btn-city').removeClass('inactiveIcon').show();
$('span', regionBtn).html(regionBtn.data('region'));
} else {
// not a single visit in this country
$$('.UserCountryMap-btn-city').addClass('inactiveIcon');
$('.map-stats').html(_.no_data);
$('.map-title').html('');
return;
} else {
flag.css({
'background': 'none'
});
$$('.UserCountryMap-btn-city').addClass('inactiveIcon').hide();
$('span', regionBtn).html(regionBtn.data('country'));
}
var mapTitle = id.length == 3 ?
UserCountryMap.countriesByIso[id].name :
$$('.userCountryMapSelectCountry option[value=' + id + ']').html(),
totalVisits = 0;
// update map title
$('.map-title').html(mapTitle);
$$('.widgetUserCountryMapvisitorMap .widgetName .map-title').html(' – ' + mapTitle);
// update total visits for that region
if (id.length == 3) {
totalVisits = UserCountryMap.countriesByIso[id]['nb_visits'];
} else if (id.length == 2) {
$.each(UserCountryMap.countriesByIso, function (iso, country) {
if (UserCountryMap.ISO3toCONT[iso] == id) {
totalVisits += country['nb_visits'];
}
});
} else {
totalVisits = self.config.visitsSummary['nb_visits'];
}
if (id.length == 3) {
$('.map-stats').html(formatValueForTooltips(UserCountryMap.countriesByIso[id], metric, 'world'));
} else {
$('.map-stats').html(
_.nb_visits.replace('%s', '<strong>' + formatNumber(totalVisits) + '</strong>') + (id != 'world' ? ' (' +
formatPercentage(totalVisits / worldTotalVisits) + ')' : '')
/*
* called by updateState if either the world or a continent is selected
*/
function renderWorldMap(target, metric) {
/**
* update the colors of the countrys
*/
function updateColorsAndTooltips(metric) {
// Create a chroma ColorScale for the selected metric that regards only the
// countries that are visible in the map.
colscale = getColorScale(UserCountryMap.countryData, metric, function (r) {
if (target.length == 2) {
return UserCountryMap.ISO3toCONT[r.iso] == target;
return true;
}
}, true);
function countryFill(data) {
var d = UserCountryMap.countriesByIso[data.iso];
if (d === null) {
return self.config.noDataColor;
} else {
// Apply the color scale to the map.
map.getLayer('countries')
.style('fill', countryFill)
.on('mouseenter', function (d, path, evt) {
if (evt.shiftKey) { // highlight on mouseover with shift pressed
path.attr('fill', '#f4f45b');
}
})
.on('mouseleave', function (d, path, evt) {
if ($.inArray(UserCountryMap.countriesByIso[d.iso].name, _rowEvolution.labels) == -1) {
path.attr('fill', countryFill(d)); // reset color
}
});
map.getLayer('countries').tooltips(function (data) {
var metric = $$('.userCountryMapSelectMetrics').val(),
country = UserCountryMap.countriesByIso[data.iso];
return '<h3>' + country.name + '</h3>' +
formatValueForTooltips(country, metric, target);
});
// if the view hasn't changed (but probably the selected metric),
// all we need to do is to recolor the current map.
if (target == self.lastSelected) {
updateColorsAndTooltips(metric);
return;
// otherwise we need to load another map svg
_updateMap(target + '.svg', function () {
// add a layer for non-selectable countries = for which no data is
// defined in the current report
map.addLayer('countries', {
name: 'context',
filter: function (pd) {
return UserCountryMap.countriesByIso[pd.iso] === undefined;
},
tooltips: function (pd) {
return '<h3>' + pd.name + '</h3>' + _.no_visit;
// add a layer for selectable countries = for which we have data
// available in the current report
map.addLayer('countries', { name: 'countryBG', filter: function (pd) {
return UserCountryMap.countriesByIso[pd.iso] !== undefined;
}});
map.addLayer('countries', {
key: 'iso',
filter: function (pd) {
return UserCountryMap.countriesByIso[pd.iso] !== undefined;
},
click: function (data, path, evt) {
evt.stopPropagation();
if (evt.shiftKey || _rowEvolution.labels.length) {
if (evt.altKey) {
path.attr('fill', '#f4f45b');
addMultipleRowEvolution('getCountry', UserCountryMap.countriesByIso[data.iso].name);
} else {
showRowEvolution('getCountry', UserCountryMap.countriesByIso[data.iso].name);
updateColorsAndTooltips(metric);
}
return;
var tgt;
if (self.lastSelected != 'world' || UserCountryMap.countriesByIso[data.iso] === undefined) {
tgt = data.iso;
} else {
tgt = UserCountryMap.ISO3toCONT[data.iso];
}
updateState(tgt);
}
updateColorsAndTooltips(metric);
}
/*
* updateMap is called by renderCountryMap() and renderWorldMap()
*/
function _updateMap(svgUrl, callback) {
map.loadMap(config.svgBasePath + svgUrl, function () {
map.clear();
self.resize();
callback();
$('.ui-tooltip').remove(); // remove all existing tooltips
}, { padding: -3});
}
function indicateLoading() {
$$('.UserCountryMap-black').show();
$$('.UserCountryMap-black').css('opacity', 0);
$$('.UserCountryMap-black').animate({ opacity: 0.5 }, 400);
$$('.UserCountryMap .loadingPiwik').show();
}
function loadingComplete() {
$$('.UserCountryMap-black').hide();
$$('.UserCountryMap .loadingPiwik').hide();
}
/*
* returns a quantifiable value for a given metric
*/
function quantify(d, metric) {
if (!metric) metric = $$('.userCountryMapSelectMetrics').val();
switch (metric) {
case 'avg_time_on_site':
return d.sum_visit_length / d.nb_visits;
case 'bounce_rate':
return d.bounce_count / d.nb_visits;
default:
return d[metric];
/*
* Aggregates a list of report rows by a given grouping function
*
* the groupBy function gets a row as argument add should return a
* group-id or false, if the row should be ignored.
*
* all rows for which groupBy returns the same group-id are
* aggregated according to the given metric.
*/
function aggregate(rows, groupBy) {
var groups = {};
$.each(rows, function (i, row) {
var g_id = groupBy ? groupBy(row) : 'X';
g_id = g_id === true ? $.isNumeric(i) && i === Number(i) ? false : i : g_id;
if (g_id) {
if (!groups[g_id]) {
groups[g_id] = {
nb_visits: 0,
nb_actions: 0,
sum_visit_length: 0,
bounce_count: 0
};
$.each(groups[g_id], function (metric) {
groups[g_id][metric] += row[metric];
});
$.each(groups, function (g_id, group) {
var apv = group.nb_actions / group.nb_visits,
ats = group.sum_visit_length / group.nb_visits,
br = (group.bounce_count * 100 / group.bounce_count);
group['nb_actions_per_visit'] = apv;
group['avg_time_on_site'] = new Date(0, 0, 0, ats / 3600, ats % 3600 / 60, ats % 60).toLocaleTimeString();
group['bounce_rate'] = (br % 1 !== 0 ? br.toFixed(1) : br) + "%";
return groupBy ? groups : groups.X;
}
function displayUnlocatableCount(unlocated, total) {
$('.unlocated-stats').html(
$('.unlocated-stats').data('tpl')
.replace('%s', unlocated)
.replace('%p', '(' + formatPercentage(unlocated / total) + ')')
.replace('%c', UserCountryMap.countriesByIso[self.lastSelected].name)
);
$('.UserCountryMap-info-btn').show();
}
/*
* renders a country map (either region or city view)
*/
function renderCountryMap(iso) {
var countryMap = {
zoomed: false,
lastRequest: false,
lastResponse: false
};
/*
* updates the colors in the current region map
* this happens once a new country is loaded and
* whenever the metric changes
*/
function updateRegionColors() {
indicateLoading();
// load data from Piwik API
ajax(_reportParams('UserCountry', 'getRegion', UserCountryMap.countriesByIso[iso].iso2))
.done(function (data) {
loadingComplete();
var regionDict = {},
totalCountryVisits = UserCountryMap.countriesByIso[iso].nb_visits,
unlocated = totalCountryVisits;
// self.lastReportMetricStats = {};
function regionCode(region) {
var key = UserCountryMap.keys[iso] || 'fips';
return key.substr(0, 4) == "fips" ? region[key].substr(2) : region[key]; // cut first two letters from fips code (=country code)
}
function regionExistsInMap(code) {
var key = UserCountryMap.keys[iso] || 'fips', q = {};
q[key] = key.substr(0, 4) == 'fips' ? UserCountryMap.countriesByIso[iso].fips + code : code;
if (map.getLayer('regions').getPaths(q).length === 0) {
return false;
}
return true;
$.each(data.reportData, function (i, row) {
regionDict[data.reportMetadata[i].region] = $.extend(row, data.reportMetadata[i], {
curMetric: quantify(row, metric)
});
var metric = $$('.userCountryMapSelectMetrics').val();
if (UserCountryMap.aggregate[iso]) {
var aggregated = aggregate(regionDict, function (row) {
var id = row.region, res = false;
$.each(UserCountryMap.aggregate[iso].groups, function (group, codes) {
if ($.inArray(id, codes) > -1) {
res = group;
}
});
return res;
//if (!UserCountryMap.aggregate.partial) regionDict = {};
$.each(aggregated, function (id, group) {
group.curMetric = quantify(group, metric);
regionDict[id] = group;
});
}
$.each(regionDict, function (key, region) {
if (regionExistsInMap(key)) unlocated -= region.nb_visits;
displayUnlocatableCount(unlocated, totalCountryVisits);
// create color scale
colscale = getColorScale(regionDict, 'curMetric', null, true);
function regionFill(data) {
var code = regionCode(data);
return regionDict[code] === undefined ? '#fff' : colscale(regionDict[code].curMetric);
}
// apply colors to map
map.getLayer('regions')
.style('fill', regionFill)
.style('stroke',function (data) {
return regionDict[regionCode(data)] === undefined ? '#bbb' : '#3C6FB6';
}).sort(function (data) {
var code = regionCode(data);
return regionDict[code] === undefined ? -1 : regionDict[code].curMetric;
}).tooltips(function (data) {
var metric = $$('.userCountryMapSelectMetrics').val(),
region = regionDict[regionCode(data)];
if (region === undefined) {
return '<h3>' + data.name + '</h3><p>' + _.nb_visits.replace('%s', '<strong>0</strong>') + '</p>';
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
}
return '<h3>' + data.name + '</h3>' +
formatValueForTooltips(region, metric, iso);
}).on('click',function (d, path, evt) {
var region = regionDict[regionCode(d)];
if (region && region.label) {
if (evt.shiftKey) {
path.attr('fill', '#f4f45b');
addMultipleRowEvolution('getRegion', region.label);
} else {
map.getLayer('regions').style('fill', regionFill);
showRowEvolution('getRegion', region.label);
}
}
}).on('mouseenter',function (d, path, evt) {
var region = regionDict[regionCode(d)];
if (region && region.label) {
if (evt.shiftKey) {
path.attr('fill', '#f4f45b');
}
}
}).on('mouseleave',function (d, path, evt) {
var region = regionDict[regionCode(d)];
if (region && region.label) {
if ($.inArray(region.label, _rowEvolution.labels) == -1) {
// reset color
path.attr('fill', regionFill(d));
}
}
}).style('cursor', function (d) {
return regionDict[regionCode(d)] && regionDict[regionCode(d)].label ? 'pointer' : 'default';
});
// check for regions missing in the map
$.each(regionDict, function (code, region) {
if (!regionExistsInMap(code)) {
console.warn('possible region mismatch!', code, region.nb_visits);
});
}
/*
* updates the city symbols in the current map
* this happens once a new country is loaded and
* whenever the metric changes
*/
function updateCitySymbols() {
// color regions in white as background for symbols
if (map.getLayer('regions')) map.getLayer('regions').style('fill', '#fff');
indicateLoading();
// get visits per city from API
ajax(_reportParams('UserCountry', 'getCity', UserCountryMap.countriesByIso[iso].iso2))
.done(function (data) {
loadingComplete();
var metric = $$('.userCountryMapSelectMetrics').val(),
colscale,
totalCountryVisits = UserCountryMap.countriesByIso[iso].nb_visits,
unlocated = totalCountryVisits,
cities = [];
// merge reportData and reportMetadata to cities array
$.each(data.reportData, function (i, row) {
unlocated -= row.nb_visits;
cities.push($.extend(row, data.reportMetadata[i], {
curMetric: quantify(row, metric)
}));
});
displayUnlocatableCount(unlocated, totalCountryVisits);
// sort by current metric
cities.sort(function (a, b) { return b.curMetric - a.curMetric; });
colscale = getColorScale(cities, metric);
// construct scale
var radscale = $K.scale.linear(cities.concat({ curMetric: 0 }), 'curMetric');
var area = map.container.width() * map.container.height(),
sumArea = 0,
f = {
nb_visits: 0.002,
nb_actions: 0.002,
avg_time_on_site: 0.02,
nb_actions_per_visit: 0.02,
bounce_rate: 0.02
},
maxRad;
$.each(cities, function (i, city) {
sumArea += isNaN(city.curMetric) ? 0 : Math.pow(radscale(city.curMetric), 2);
});
maxRad = Math.sqrt(area * f[metric] / sumArea);
radscale = $K.scale.sqrt(cities.concat({ curMetric: 0 }), 'curMetric').range([2, maxRad + 2]);
var is_rate = metric.substr(0, 3) != 'nb_' || metric == 'nb_actions_per_visit';
var citySymbols = map.addSymbols({
type: Kartograph.LabeledBubble,
data: cities,
clustering: 'noverlap',
clusteringOpts: {
size: 128,
tolerance: 0
},
title: function (d) {
return radscale(d.curMetric) > 10 ? formatNumber(d.curMetric) : '';
},
labelattrs: {
fill: '#fff',
'font-size': 11,
stroke: false,
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
},
filter: function (d) {
if (isNaN(d.lat) || isNaN(d.long)) return false;
return is_rate ? d.nb_visits > 5 && d.curMetric : d.curMetric;
},
aggregate: function (rows) {
var row = aggregate(rows);
row.city_names = [];
row.label = rows[0].label; // keep label of biggest city for row evolution
$.each(rows, function (i, r) {
row.city_names = row.city_names.concat(r.city_names ? r.city_names : [r.city_name]);
});
row.city_name = row.city_names[0] + (row.city_names.length > 1 ? ' ' + _.and_n_others.replace('%s', (row.city_names.length - 1)) : '');
row.curMetric = quantify(row, metric);
return row;
},
sortBy: 'radius desc',
location: function (city) { return [city.long, city.lat]; },
radius: function (city) { return radscale(city.curMetric); },
tooltip: function (city) {
return '<h3>' + city.city_name + '</h3>' +
formatValueForTooltips(city, metric, iso);
},
attrs: function (city) {
return {
fill: colscale(city.curMetric).hex(),
'fill-opacity': 0.7,
stroke: '#fff',
cursor: 'pointer'
};
},
mouseenter: function (city, symbol, evt) {
symbol.path.attr({
'fill-opacity': 1,
'stroke': '#000000',
'stroke-opacity': 1,
'stroke-width': 2
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
if (evt.shiftKey) {
symbol.path.attr({ fill: '#f4f45b' });
if (symbol.label) symbol.label.attr({ fill: '#000' });
}
},
mouseleave: function (city, symbol) {
symbol.path.attr({
'fill-opacity': 0.7,
'stroke-opacity': 1,
'stroke-width': 1,
'stroke': '#ffffff'
});
if ($.inArray(city.label, _rowEvolution.labels) == -1) {
symbol.path.attr({ fill: colscale(city.curMetric) });
if (symbol.label) symbol.label.attr({ fill: '#fff' });
}
},
click: function (city, symbol, evt) {
if (evt.shiftKey) {
addMultipleRowEvolution('getCity', city.label);
symbol.path.attr('fill', '#f4f45b');
if (symbol.label) symbol.label.attr('fill', '#000');
} else {
showRowEvolution('getCity', city.label);
citySymbols.update({
attrs: function (city) {
return { fill: colscale(city.curMetric) };
}
});
}
_updateMap(iso + '.svg', function () {
// add background
map.addLayer('context', {
key: 'iso',
filter: function (pd) {
return UserCountryMap.countriesByIso[pd.iso] === undefined;
}
});
map.addLayer('context', {
key: 'iso',
name: 'context-clickable',
filter: function (pd) {
return UserCountryMap.countriesByIso[pd.iso] !== undefined;
},
click: function (path, p, evt) { // add click events for surrounding countries
evt.stopPropagation();
updateState(path.iso);
},
tooltips: function (data) {
if (UserCountryMap.countriesByIso[data.iso] === undefined) {
return 'no data';
}
var metric = $$('.userCountryMapSelectMetrics').val(),
country = UserCountryMap.countriesByIso[data.iso];
return '<h3>' + country.name + '</h3>' +
formatValueForTooltips(country, metric, 'world');
}
});
function isThisCountry(d) { return d.iso == iso;}
map.addLayer("context", {
name: "regionBG",
filter: isThisCountry
});
map.addLayer("context", {
name: "regionBG-fill",
filter: isThisCountry
});
map.addLayer('regions', {
key: 'fips',
styles: {
stroke: '#aaa'
},
click: function (d, p, evt) {
evt.stopPropagation();
}
});
function filtCountryLabels(data) {
return data.iso != iso &&
map.getLayer('context-clickable') &&
map.getLayer('context-clickable').getPath(data.iso) &&
Math.abs(map.getLayer('context-clickable').getPath(data.iso).path.area()) > 700;
// returns either the reference to the country polygon or a custom label
// position if defined in UserCountryMap.customLabelPositions
function countryLabelPos(data) {
var CLP = UserCountryMap.customLabelPositions;
if (CLP[iso] && CLP[iso][data.iso]) return CLP[iso][data.iso];
return 'context-clickable.' + data.iso;
map.addSymbols({
data: map.getLayer('context-clickable').getPathsData(),
type: $K.Label,
filter: filtCountryLabels,
location: countryLabelPos,
text: function (data) { return UserCountryMap.countriesByIso[data.iso].iso2; },
'class': 'countryLabelBg'
});
map.addSymbols({
data: map.getLayer('context-clickable').getPathsData(),
type: $K.Label,
filter: filtCountryLabels,
location: countryLabelPos,
text: function (data) { return UserCountryMap.countriesByIso[data.iso].iso2; },
'class': 'countryLabel'
});
if (!UserCountryMap.countriesByIso[iso]) return;
updateRegionColors();
} else {
updateCitySymbols();
}
});
var _rowEvolution = { labels: [], method: false };
function addMultipleRowEvolution(method, label) {