matomo-org / matomo

Empowering People Ethically with the leading open source alternative to Google Analytics that gives you full control over your data. Matomo lets you easily collect data from websites & apps and visualise this data and extract insights. Privacy is built-in. Liberating Web Analytics. Star us on Github? +1. And we love Pull Requests!
https://matomo.org/
GNU General Public License v3.0
19.83k stars 2.64k forks source link

UserCountryMap: zoomable world map of your visitors location #1514

Closed gka closed 13 years ago

gka commented 14 years ago

This plugin adds a worldmap widget to your dashboard. The map is rendered in flash (swf size: 216kb) and has no dependencies to other services like google maps.

the plugin load the data via API call from UserCountry plugin.

by now, the plugin is able to dsiplay the following data:

I built in PNG export and tried to adapt the look and feel of the OpenFlashCharts used in the DataTableView component.

gka commented 14 years ago

Attachment: minor bugfixes: filter_limit=-1, mac/linux font issues, multiline tooltips UserCountryMap.zip

gka commented 14 years ago

Attachment: a few suggestions for the design of the map legend piwik-legend-layouts.zip

gka commented 14 years ago

Attachment: second iteration of legend layout, this time with a vertical bar that has a fixed position at the lower left, which should look fine for all continents piwik-legend-layout-left.zip

gka commented 14 years ago

Attachment: added error message for #1088 UserCountryMap-0.5.zip

mattab commented 14 years ago

Attachment: Map is empty but countries widget works Piwik Web Analytics Reports.png

mattab commented 14 years ago

Attachment: XML returned (seen in firebug) but map is blank index.php.xml

gka commented 14 years ago

Attachment: UserCountryMap-0.6.zip

gka commented 14 years ago

Attachment: updated screenshot, now with legend mapplugin.png

mattab commented 14 years ago

Attachment: Bug on IE.PNG

gka commented 14 years ago

Attachment: version 0.7: added fullscreen button, displaying buttons through flash instead of javascript to get rid of the flashplayers security exceptions on fullscreen UserCountryMap-0.7.zip

halfdan commented 14 years ago

Greg, that thing is beautiful! Well done!

Apart from that it looks just great! Did you develop the PiwikMap.swf and if not how is it licensed?

gka commented 14 years ago

thanks :)

"General_ExportAsImage" is part of the german translation and seems to be missing in the english lang file. Maybe this isn't a bug of my plugin.

The second bug is now fixed by setting the flashplayers wmode to "opaque".

Yes, I developed the PiwikMap.swf by myself. The only external library I used is the as3corelib (http://code.google.com/p/as3corelib/), which is released under New BSD license.

anonymous-matomo-user commented 14 years ago

Great Plugin and a real alternative to the buggy Maps plugin. A question: Do you plan to enhance functionality, maybe with use of plugin GeoIP so that it is able to show the users based on cities?

gka commented 14 years ago

Good idea. I haven't looked at the GeoIP plugin yet. If it turns out that it works fine and there some kind of xml api to request city data for a given country, I'll try to include this in the UserCountryMap plugin.

anonymous-matomo-user commented 14 years ago

wow. I really like this new widget! Perfect. Thank you!

mattab commented 14 years ago

Very nice work! Great job also on getting the open source map. I think this is unique as far as I know!

I have a few questions

gka commented 14 years ago

Ok, I'll try to get through your questions

There is one more thing I would like to add to the plugin. I just looked at the GeoIP plugin and wouldn't it be nice to add a detailed country-view in which all visitor cities are displayed like in GA? This would require more thinking about the zooming interface, but it shouldn't be a big problem.

Do you know if the GeoIP plugin is going into Piwik core as well? I tested the plugin but while it correctly updates the tables with the city/lat/long information, it seems that the rest of the plugin doesn't work right (API call returns no data, etc).

mattab commented 14 years ago

Answers

Looking forward to your update!

gka commented 14 years ago

Updates:

To answer your question on the zoom levels, by now there are only zwo zoom level: world + region, where region means almost the same as continent, except for africa and asia (which are both split into two parts for better zooming experience).

mattab commented 14 years ago

Excellent updates. Thanks for fixing it quickly :)

For example in trunk

http://localhost/trunk/index.php?module=API&method=API.getProcessedReport&idSite=1&apiModule=UserCountry&apiAction=getCountry&format=xml&token_auth=0b809661490d605bfd77f57ed11f0b14&language=en&date=today&period=year

returns:

<?xml version="1.0" encoding="utf-8" ?>
<result>
    <website>BuyForSeniors</website>
    <prettyDate>2009</prettyDate>
    <metadata>
        <category>Visitors</category>
        <name>Country</name>
        <module>UserCountry</module>

        <action>getCountry</action>
        <dimension>Country</dimension>
        <metrics>
            <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
            <nb_visits>Visits</nb_visits>
            <nb_actions>Actions</nb_actions>

        </metrics>
        <processedMetrics>
            <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
            <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
            <bounce_rate>Bounce Rate</bounce_rate>
            <conversion_rate>Conversion Rate</conversion_rate>

        </processedMetrics>
        <metricsGoal>
            <nb_conversions>Conversions</nb_conversions>
            <conversion_rate>Conversion Rate</conversion_rate>
            <revenue>Revenue</revenue>

        </metricsGoal>

        <processedMetricsGoal>
            <revenue_per_visit>Value per Visit</revenue_per_visit>

        </processedMetricsGoal>
        <uniqueId>UserCountry_getCountry</uniqueId>

    </metadata>
    <columns>
        <label>Country</label>

        <nb_visits>Visits</nb_visits>
        <nb_actions>Actions</nb_actions>
        <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
        <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
        <bounce_rate>Bounce Rate</bounce_rate>
        <conversion_rate>Conversion Rate</conversion_rate>

        <revenue>Revenue</revenue>

    </columns>
    <reportData>
        <row>
            <label>France</label>
            <nb_visits>12634</nb_visits>
            <nb_actions>12634</nb_actions>

            <revenue> 0</revenue>
            <conversion_rate>6.28%</conversion_rate>
            <nb_actions_per_visit>1</nb_actions_per_visit>
            <avg_time_on_site>00:00:10</avg_time_on_site>
            <bounce_rate>100%</bounce_rate>

        </row>
[...........]

In trunk currently we don't display anymore the (useless) raw metrics: bounce count, total time spent. but instead we display interesting processed metrics from these (bounce rate and avg visit duration). By using this magic API method instead of direct call, you ensure that all the data is pre-processed and columns are set properly (with the %, the revenue sign, right precision, etc.) all consistent with other reports.

Let me know if you have any question. Appart from that, code looks good and I'll be happy to commit :)

gka commented 14 years ago

Yes, there is a question. I understand that the metadata API is a huge time-saver for many developers, but isn't it a violation of the MVC pattern? As the view code of my plugin would have no more access to the raw data, it would have to re-parse this data out of the formated strings. For example, the SWF map needs the avg_time_on_site value two times: once to calculate the right color for the countries and once to show it in the tooltip. To do the color-calculation, it now would have to parse the total number of seconds out of the HH:MM:SS string. Or even worse, it would have to guess the countries iso-code out of it's translated name. I don't think this is the intention of the metadata API. Is there a way to force it to include both the raw (but preprocessed) values (ids, integers, floats) and the formatted values (translated and formatted strings)? Something like this would be helpful:

<reportData>
    <row>
        <label value="fr">France</label>
        <nb_visits value="12634">12634</nb_visits>
        <nb_actions value="12634">12634</nb_actions>

        <revenue value="0.0"> 0</revenue>
        <conversion_rate value="0.0628">6.28%</conversion_rate>
        <nb_actions_per_visit value="1">1</nb_actions_per_visit>
        <avg_time_on_site value="10">00:00:10</avg_time_on_site>
        <bounce_rate value="1">100%</bounce_rate>
    </row>
gka commented 14 years ago

Another remark (which may be off-topic here): I don't think that the avg. time on website is a sufficient replacement for the total time on website. Both values are very interesting on its own. Also there isn't any other replacement of the total time on the site, neither the number of unique visitors nor the number of actions. Therefore I would suggest to keep the total time on site inside the new metadata API.

anonymous-matomo-user commented 14 years ago

I think the bar design for the legend is better than the block version.

mattab commented 14 years ago
[...]
    <reportMetadata>

        <row>
            <code>fr</code>
            <logo>plugins/UserCountry/flags/fr.png</logo>
            <logoWidth>18</logoWidth>
            <logoHeight>12</logoHeight>

        </row>

        <row>
            <code>gb</code>
            <logo>plugins/UserCountry/flags/gb.png</logo>
            <logoWidth>18</logoWidth>
            <logoHeight>12</logoHeight>

        </row>
[..]
<bounce_rate>100%</bounce_rate>
<revenue> 0</revenue>

Others

Keep up great work and let me know your thoughts

gka commented 14 years ago
gka commented 14 years ago

I think I need a newer version of the API plugin. Mine doesn't include a method called getProcessedReport and it isn't listed in the api reference yet.

gka commented 14 years ago

Ok, this seems to be the right time to checkout the svn repository :)

mattab commented 14 years ago

Are there other bug reports that you can think of? Did you try in different languages containing non standard characters?

gka commented 14 years ago

I updated the repository and the new API now works. One thing I don't understand is that there is a column definition for _nb_uniqvisitors below <metadata><metrics>, but there is neither a corresponding column in <columns> nor is there some data below <reportData><row>. Is it simply missing?

mattab commented 14 years ago

I installed the new update but map won't load, and the error is displayed: error while loading contacts (unknown error #1088)

Does it work for you?

gka commented 14 years ago

error 1088 occurs if the xml document is not well-formed. it works for me.

mattab commented 14 years ago

Error was in the en.php file (missing comma)

But it still doesn't data for me even though I see the successful XML request (check out the file attached, maybe it's too much or something??).

mattab commented 14 years ago

Thought for future: it would be great to have the same refactored SELECT metrics option list in all Piwik graphs (vertical bar, pie chart). Having the graph only plot visits at the moment has always bugged me and your SELECT looks like the perfect solution!

gka commented 14 years ago

I tested your xml file and it worked fine. I also checked out the latest Piwik from SVN and it keeps working. Maybe it has to do something with the Apache or PHP settings. The flash xml parser is very strict and it goes down on the smallest charset issues. We got to fix that bug.

Nevertheless I now finished version 0.6. The main changes are:

mattab commented 14 years ago

Hi greg, great work. Can you please email me at matt att piwik.org to discuss the next steps?

mattab commented 14 years ago

It's working now for me, not sure what the problem was before. Feedback

anonymous-matomo-user commented 14 years ago

after update to 0.6 I get following error:

Fatal error: Cannot use string offset as an array in /users/jmp/www/piwik/plugins/UserCountryMap/UserCountryMap.php on line 88
mattab commented 14 years ago
gka commented 14 years ago

@matt:

@beatgarantie:

Do you have the latest version of the API plugin installed? The plugin makes a internal api request on API.getMetadata().

anonymous-matomo-user commented 14 years ago

@greg: so, I have to wait for Piwik 0.6.5 to get the new API. Thank you for your fine widget.

gka commented 14 years ago

I just checked in the plugin into svn :)

mattab commented 14 years ago

Great stuff!

mattab commented 14 years ago

(In [2718]) Refs #1514 Minor updates, comments and Core credits. Also including translations in main translation files.

Also updated http://piwik.org/the-piwik-team/ and listed Gregor Aisch in the list of Contributors

mattab commented 14 years ago

(In [2809]) Refs #1514

anonymous-matomo-user commented 14 years ago

Hello Gregor, can you help me finding the package org.piwik.data.Metric? I need this to compile and test a possible fix to #1634.

anonymous-matomo-user commented 14 years ago

Here's a little patch to make the map show up in the Location&Provider submenu. It requires patching some files from Provider and GeoIP, unfortunately I could not find a way around that. Also, it is suffering from a bug in firefox, where flash content which has both with and height attributes in percent is not displayed after loading, but when the site is rendered anew, for example opening/closing firebug. I tried a few workarounds I found on the net, but none of them seem to work here.

* plugins/UserCountryMap/UserCountryMap.php
  replace function postLoad with:
  function postLoad()
  {
    Piwik_AddWidget('General_Visitors', Piwik_Translate('UserCountry_WidgetCountries').' ('.Piwik_Translate('UserCountryMap_worldMap').')', 'UserCountryMap', 'worldMap');
    Piwik_AddAction('template_headerUserCountry', array('Piwik_UserCountryMap','headerUserCountryMap'));
    Piwik_AddAction('template_footerUserCountry', array('Piwik_UserCountryMap','footerUserCountryMap'));
  }

  modify function worldMap:
  function worldMap($template='worldmap')
  {
    $view = Piwik_View::factory($template);

  add three functions in class Piwik_UserCountryMap:
  static public function headerUserCountryMap($notification)
  {
    $out =& $notification->getNotificationObject();
    $out = '<div id="leftcolumn">';
  }

  static public function footerUserCountryMap($notification)
  {
    $out =& $notification->getNotificationObject();
    $out = '</div>
      <div id="rightcolumn">';
    $out .= '<h2>'.Piwik_Translate('UserCountry_WidgetCountries').' ('.Piwik_Translate('UserCountryMap_worldMap').')</h2>';
    $out .= Piwik_FrontController::getInstance()->fetchDispatch('UserCountryMap','worldMapMenu');

    $PlugIns=Piwik_PluginsManager::getInstance()->getLoadedPlugins();
    if (!array_key_exists('Provider',$PlugIns))
    {
      $out.='</div>';
    }
  }

  function worldMapMenu()
  {
    $this->worldMap('worldmap_menu'); 
  }

* plugins/UserCountryMap/templates/worldmap.tpl
  modify line with swfobject.embedSWF, replace
  Math.round($('#UserCountryMap_content').width() *.55)
  with
  (Math.round($('#UserCountryMap_content').width() *.55)) || "55%"

  copy file to worldmap_menu.tpl, add in wordmap_menu.tpl two lines:

  at the beginning, this being the first line:
  {postEvent name="template_headerUserCountryMap"}

  at the end, this being the last line:
  {postEvent name="template_footerUserCountryMap"}

* plugins/Provider/Provider.php
  replace function postLoad with:
  function postLoad()
  {
    $PlugIns=Piwik_PluginsManager::getInstance()->getLoadedPlugins();
    if (array_key_exists('UserCountryMap',$PlugIns))
    {
      Piwik_AddAction('template_footerUserCountryMap', array('Piwik_Provider','footerUserCountryMap'));
    }
    else
    {
      Piwik_AddAction('template_headerUserCountry', array('Piwik_Provider','headerUserCountry'));
      Piwik_AddAction('template_footerUserCountry', array('Piwik_Provider','footerUserCountry'));
    }

  }

  add function:
  static public function footerUserCountryMap($notification)
  {
    $out =& $notification->getNotificationObject();
    $out='<h2>'.Piwik_Translate('Provider_WidgetProviders').'</h2>';
    $out .= Piwik_FrontController::getInstance()->fetchDispatch('Provider','getProvider');
    $out .= '</div>';
  }

* plugins/GeoIP/Controller.php
  replace
  $view->provider = Piwik_FrontController::getInstance()->fetchDispatch('Provider','getProvider');
  with:
  $PlugIns=Piwik_PluginsManager::getInstance()->getLoadedPlugins();
  if(array_key_exists('Provider',$PlugIns))
  {
    $view->provider = Piwik_FrontController::getInstance()->fetchDispatch('Provider','getProvider');
  }
  if(array_key_exists('UserCountryMap',$PlugIns))
  {
    $view->usercountrymap = Piwik_FrontController::getInstance()->fetchDispatch('UserCountryMap','worldMap');
  }

* plugins/GeoIP/templates/index.tpl
  replace

  <h2>Provider</h2>
  {if isset($provider)}
  {$provider}
  {/if}

  with:

  {if isset($usercountrymap)}
  <h2>{'UserCountry_WidgetCountries'|translate} ({'UserCountryMap_worldMap'|translate})</h2>
  {$usercountrymap}
  {/if}
  {if isset($provider)}
  <h2>{'Provider_WidgetProviders'|translate}</h2>
  {$provider}
  {/if}

* lang/de.php
  add:
  'UserCountry_country_' => 'Unbekannt',
  'UserCountry_continent_' => 'Unbekannt',

* lang/en.php
  add:
  'UserCountry_country_' => 'Unknown',
  'UserCountry_continent_' => 'Unknown',
robocoder commented 14 years ago

The changes to lang/*.php are wontfix. The problem is that GeoIP should return "xx" instead of "".

Note: this ticket is closed. Please open a new ticket (or an existing one for map improvements).

mattab commented 13 years ago

see the ticket to include world map in the country report: #1821