Google chartlive

Материал из Dom.

Перейти к: навигация, поиск

[править] Проект Google chartlive - некрасивые графики посещаемости вашего сайта малой кровью

Насмотревшись на "невероятную" посещаемость моего сайта в google analytics (черт, как мне нравится эта штука!) я решил создать сервис-надстройку над google analytics. К сожалению google не представляет никакого api для доступа к своим отчетам. Не понятно будет ли когда либо предприняты шаги в этом направлении. По крайней мере в интернете я нашел пару решений, которые имитировали действия пользователя на сайте и "как-будто-бы-это-сам-пользователь" заходит в свой рабочий кабинет и жмет кнопку "Экспортировать даннные". Я попробовал одно из таких творений (greqo-analytics-alpha, от Tom-а Klenwell-а). Увы и ах, но время работы этого скрипта было порядка 40 с гаком секунд, несерьезно. У меня на этом бесплатном хостинге лимит в сего 4 секунды, можно было бы разместить скрипт где-нибудь из множества дружественных мне сайтов (задание для cron, которое бы выкачивало бы аналитику один раз в день), но потом я решил что это решение не для настоящего кулхацкера и сейчас я думаю над решением этой проблемы. Как только я найду приемлимое решение, то обязательно сюда сообщу. А пока решение только частичное.

Изображение:google_chart_live_1.png

В любом случае я написал и предлагаю вам попользоваться следующим скриптом:

В шаблон mediawiki я добавил подключение следующих файлов:

<link rel="stylesheet" type="text/css" href="/mediawiki/extensions/chartvisits/st.css" />
 <script src="/mediawiki/extensions/chartvisits/charts_splash.js" > </script>

Затем где то там в теле страницы я разместил маленький баннер-плейсхолдер:

<div id="p-search" class="portlet">
		<h5 id="heading_line_visitors">Visitors Actiity</h5>
		<div class="pBody">
                        <a href="javascript:make_toggle_big_chart()">
				<img border="0" id="chart_visitors_activity" 
src="/mediawiki/extensions/chartvisits/google_pars.php" alt="" />
			</a>
		</div>
	</div>

Ссылка на файл /mediawiki/extensions/chartvisits/google_pars.php - генерирует график - это поведение по-умолчанию, однако все можно изменить. Так указав параметр:

kind = image или xml - мы выбираем формат отчета
sparkline = VisitsSparkline или PageviewsSparkline или AvgPageSparkline 
или TimeOnSiteSparkline или BounceRateSparkline или NewVisitsSparkline или AvgPageviewsSparkline 
- я указываю тип графика который нужен
w - задает ширину графика, но в отрезке от 40 - 800 пиксклей
h - задает высоту графика, но в отрезке от 40 - 640 пикселей

Отчеты в формате xml от google analytics я складываю в папку:

/mediawiki/extensions/chartvisits/hist

Всегда при построении графика ищется отчет за последнюю дату

Вот исходный код файла php, который все это строит:

<?php
 
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past
 
 
$result_output_type = 'image';
if (isset($_REQUEST['kind'])){
	$result_output_type = $_REQUEST['kind'];
}
if ($result_output_type != 'xml' && $result_output_type != 'image')
$result_output_type = 'image';
 
 
// ******************************************************
$good_sparklines = array (
'VisitsSparkline', 'PageviewsSparkline', 'AvgPageSparkline', 'TimeOnSiteSparkline', 'BounceRateSparkline', 'NewVisitsSparkline',
'AvgPageviewsSparkline',
);
$used_sparkline = 'VisitsSparkline';
if (isset($_REQUEST['sparkline'])){
	$used_sparkline = $_REQUEST['sparkline'];
}
if (! in_array($used_sparkline, $good_sparklines))
	$used_sparkline = 'VisitsSparkline';
 
// ****************************************************************
$PIC_WIDTH = 140;
$PIC_HEIGHT = 60;
if (isset($_REQUEST['w']) && is_numeric($_REQUEST['w'])){
	$PIC_WIDTH = max(40, min(intval($_REQUEST['w']), 800));
}
if (isset($_REQUEST['h']) && is_numeric($_REQUEST['h'])){
	$PIC_HEIGHT =max(40, min(intval($_REQUEST['h']), 640));
}
 
 
 
 
 
 
 
function dexport ($x){die ('<pre>'.var_export($x, true). '</pre>');}
 
function makeSimpleArrayPlot ($plot_data){
	return join('.', $plot_data);
}
 
function makeFatality ($kind, $error_type = false, $error_msg = false){
	if ($kind == 'image'){
		header ('Content-Type: image/png');
		$img = imagecreatefrompng(dirname(__FILE__) . '/pics_no_files.png');
		imagepng($img);
		imagedestroy($img);
	}
	else{
		header('Content-Type: text/xml');
		print ('<?xml version="1.0" encoding="windows-1251" ?>
			<result status="error" error_type="'.$error_type.'">
			<error-message><![CDATA['.$error_msg.']]></error-message>
			</result>
			');
	}
	die ();
}
 
include_once('xml_pars_support.php');
$fname = null;
 
$dir = dirname(__FILE__) . '/hist/';
$dh = opendir($dir);
$arr_names = array ();
while ( ($file = readdir($dh))) {
	$fullname = $dir . $file;
	if ($file == '..' || $file == '.')
	continue;
	if (is_dir($full_msg))
	continue;
	$arr_got = array ();
	if (! preg_match('/Analytics_.+_(\d+)-(\d+)_/i', $file , $arr_got)){
		continue;
	}
	$arr_names [] = array ('fullname' => $fullname, 'start_range' => $arr_got[1], 'end_range' => $arr_got[2]);
}
 
function get_flat_date  ($a){
	$arr_got = array ();
	if (preg_match('/^(\d{4})(\d{2})(\d{2})/i', $a, $arr_got))
	return mktime(0,0,0, $arr_got[2], $arr_got[3], $arr_got[1]);
	else
	return mktime(0,0,0,0,0,0);
}
 
function cmp_by_ranges_3454_dsf_234 ($a , $b){
	$a = $a ['end_range'];
	$b = $b ['end_range'];
 
	$a = get_flat_date ($a);
	$b = get_flat_date ($b);
 
	if ($a < $b) return -1;
	if ($a > $b) return +1;
	return 0;
}
 
usort($arr_names, 'cmp_by_ranges_3454_dsf_234');
 
 
 
if (count($arr_names) == 0){
	makeFatality ($result_output_type , 'no_files');
}
$fname = $arr_names [count($arr_names) - 1]['fullname'];
 
$end_date_range = $arr_names [count($arr_names) - 1]['end_range'];
$end_date_range = get_flat_date($end_date_range);
 
 
 
 
$xml = join (file ($fname));
$xml = xml2array ($xml);
 
$sparks = xml2array_getxpath_list ($xml, 'AnalyticsReport/Report/Sparkline');
if (count($sparks) == 0){
	makeFatality ($result_output_type, 'bad_file');
}
 
$plot_data = array ();
for ($i = 0; $i < count($sparks); $i++){
	$spark = $sparks [$i];
	if (isset($spark['attributes']) && isset($spark['attributes']['id']) && $spark['attributes']['id'] == $used_sparkline ){
 
		$plot_data_tags = xml2array_getxpath_list ($spark, 'Sparkline/PrimaryValue');
		//dexport($plot_data_tags);
		for ($j = 0; $j < count($plot_data_tags); $j++){
			//$plot_data [] = preg_replace('|\..*$|', '',  get_elt_array($plot_data_tags[$j], 'text'));
			$plot_data [] = floatval(get_elt_array($plot_data_tags[$j], 'text'));
		}// --- for ---
		break;
	}// --- if ---
}// -- for --
 
$COUNT_OF_POINTS = 10;
 
$pos_from = 0;
if (count($plot_data) > $COUNT_OF_POINTS)
$plot_data = array_slice($plot_data, count ($plot_data) - $COUNT_OF_POINTS);
 
$max_plot = max ($plot_data);
$min_plot = min ($plot_data);
 
$end_date_range = strtotime('-'.count($plot_data).' day', $end_date_range);
 
 
 
if ($result_output_type == 'image'){
 
	$koeff_x = ($PIC_WIDTH-20) / count($plot_data);
	$koeff_y = ($PIC_HEIGHT-20) / ($max_plot - $min_plot + 2);
 
 
	$img = imagecreatetruecolor($PIC_WIDTH, $PIC_HEIGHT);
 
	$background_color = imagecolorallocate($img, 155, 255, 68);
	$line_color = imagecolorallocate($img, 0, 0, 0);
	$grid_color = imagecolorallocate($img, 65, 146, 252);
 
	imagefilledrectangle($img, 0,0, $PIC_WIDTH, $PIC_HEIGHT, $background_color);
 
	$box_outer_x_start = 0;
	$box_outer_x_end = 0;
	for ($i = 0; $i < count($plot_data) - 1; $i++){
		$x_start = round(15 + $i*$koeff_x);
		$y_start = $PIC_HEIGHT -  round(10 + ($plot_data[$i]  - $min_plot)*$koeff_y);
 
		$x_end = round(15 + ($i+1)*$koeff_x);
		$y_end = $PIC_HEIGHT - round(10 + ($plot_data[$i+1]- $min_plot)*$koeff_y);
 
		//die('x = ' . $x_start . ' y = ' . $y_start . ' x2 = '. $x_end . ' y2 = ' . $y_end);
		imagesetthickness($img , 1);
		imageline($img , $x_start, 0, $x_start, $PIC_HEIGHT, $grid_color);
 
		imagesetthickness($img , 2);
		imageline($img , $x_start, $y_start, $x_end, $y_end, $line_color);
		imagefilledellipse($img, $x_start, $y_start ,5, 5, $line_color);
 
 
		if ($i == 0){
			imagestring($img , 1, $x_start - 10,  $y_start - 5 , $plot_data[$i], $line_color);
			$box_outer_x_start = $x_start;
		}
		if ($i == (count($plot_data) - 2)){
			imagesetthickness($img , 1);
			imageline($img , $x_end, 0, $x_end, $PIC_HEIGHT, $grid_color);
			imagefilledellipse($img, $x_end, $y_end,5, 5, $line_color);
 
			imagestring($img , 1, $x_end + 5,  $y_end - 5 , $plot_data[$i+1], $line_color);
 
			$box_outer_x_end = $x_end;
		}
 
	}
 
	$y_start = $PIC_HEIGHT -  round(10 + ($min_plot  - $min_plot)*$koeff_y);
	$y_end = $PIC_HEIGHT -  round(10 + ($max_plot  - $min_plot)*$koeff_y);
 
	imagesetthickness($img , 1);
	imageline($img , $box_outer_x_start, $y_start, $box_outer_x_end, $y_start, $grid_color);
	imageline($img , $box_outer_x_start, $y_end, $box_outer_x_end, $y_end, $grid_color);
 
 
	setcookie('plot_x', makeSimpleArrayPlot ($plot_data), time() + 3600);
 
	header ('Content-Type: image/png');
	imagepng($img);
	imagedestroy($img);
}
elseif ($result_output_type == 'xml'){
 
	header('Content-Type: text/xml');
	print '<?xml version="1.0" encoding="windows-1251" ?>'. "\n";
	print '<plot>' . "\n";
	for ($i = 0; $i < count($plot_data) ; $i++){
		$date_i = date ('Y-m-d', strtotime('+'.($i+1).' day', $end_date_range)); 
 
		print '<point x="'.$date_i.'" y="'.$plot_data[$i].'" />' . "\n";
	}
	print '</plot>'. "\n";
}
 
?>

Для работы данному файлу необходима маленькая библиотечка для работы с xml|xpath, т.к. при словосочетании "встроенная поддержка xml в php" меня начинает тошнить, то пришлось взять и сделать небольшую собственную библиотечку выполняющую разбор xml с помощью regexp-ов. В основе своей код был взят на php.net (автора я боюсь сейчас не найду), затем переработана и дополнена.

<?php
function get_elt_array ($arr , $name){
	return $arr[$name];
}
 
function xml2array ($xml){
	$xmlary = array ();
	$ReElements = '/<(\w+)\s*([^\/>]*)\s*(?:\/>|>(.*?)<(\/\s*\1\s*)>)/s';
	$ReAttributes = '/(\w+)=(?:"|\')([^"\']*)(:?"|\')/';
	preg_match_all ($ReElements, $xml, $elements);
	foreach ($elements[1] as $ie => $xx) {
		$xmlary[$ie]["name"] = $elements[1][$ie];
		if ( $attributes = trim($elements[2][$ie])) {
			preg_match_all ($ReAttributes, $attributes, $att);
			foreach ($att[1] as $ia => $xx)
			// all the attributes for current element are added here
			$xmlary[$ie]["attributes"][$att[1][$ia]] = $att[2][$ia];
		} // if $attributes
		// get text if it's combined with sub elements
		$cdend = strpos($elements[3][$ie],"<");
		if ($cdend > 0) {
			$xmlary[$ie]["text"] = substr($elements[3][$ie],0,$cdend -1);
		} // if cdend
 
		if (preg_match ($ReElements, $elements[3][$ie])){
			$xmlary[$ie]["elements"] = xml2array ($elements[3][$ie]);
		}
		else if (isset($elements[3][$ie])){
			$xmlary[$ie]["text"] = $elements[3][$ie];
		}     $xmlary[$ie]["closetag"] = $elements[4][$ie];
	}
	//foreach ?
	return $xmlary;
}
 
 
function xml2array_getxpath ($xml_array , $xpath_expr){
	$elts = explode('/', $xpath_expr);
	$elts2 = array ();
	for ($i = 0; $i < count($elts); $i++){
		$tmp = trim($elts [$i]);
		if ($tmp == '') continue;
		$elts2 [] = $tmp;
	}
	if (count($xml_array) != 0 && isset($xml_array[0]))
	return rec_xml2array_getxpath ($xml_array[0] , $elts2, false);
	else
	return rec_xml2array_getxpath ($xml_array , $elts2, false);
}
 
function xml2array_getxpath_list ($xml_array , $xpath_expr){
	$GLOBALS['tmp_xpath_cached_23432'] = array ();
	$elts = explode('/', $xpath_expr);
	$elts2 = array ();
	for ($i = 0; $i < count($elts); $i++){
		$tmp = trim($elts [$i]);
		if ($tmp == '') continue;
		$elts2 [] = $tmp;
	}
	if (count($xml_array) != 0 && isset($xml_array[0]))
	rec_xml2array_getxpath ($xml_array[0] , $elts2, 'tmp_xpath_cached_23432');
	else
	rec_xml2array_getxpath ($xml_array , $elts2, 'tmp_xpath_cached_23432');
	return 	$GLOBALS['tmp_xpath_cached_23432'];
}
 
 
function rec_xml2array_getxpath ($xml_array , $xpath_expr_arr, $target_name){
	if ($xml_array == null) return false;
	if (count($xpath_expr_arr) != 0 && count($xml_array) == 0 ) return false;
	if (count($xpath_expr_arr) == 0) {
		if ($target_name)
		$GLOBALS[$target_name] [] = $xml_array;
		return $xml_array;
	}
	if (count($xpath_expr_arr) == 1 && isset($xml_array['name']) && $xml_array['name'] == $xpath_expr_arr[0]){
		if ($target_name)
		$GLOBALS[$target_name] [] = $xml_array;
		return $xml_array;
	}
 
 
	$first = array_shift($xpath_expr_arr);
	if (strpos($first, '@') !== false){
		$clear_attrname = substr($first , 1);
		$attrs = $xml_array ['attributes'];
		foreach ($attrs as $key => $value){
			if ($key == $clear_attrname) {
				if ($target_name)
				$GLOBALS[$target_name] [] = $value;
				return $value;
			}
		}
		return false;
	}
 
 
	if (strpos($first , '[') !== false){
		$arr_got = array ();
		preg_match('/(.*)\[(d+)\]/' , $first , $arr_got);
		$idx = -1;
		for ($i = 0; $i < count ($xml_array); $i++)
		if ($xml_array [$i]['name'] == $first){
			$idx ++;
			if ($idx == $arr_got [2]){
				$rezzz = rec_xml2array_getxpath ($xml_array [$i]['elements'] , $xpath_expr_arr, $target_name);
				if (! $target_name)
				return $rezzz;
			}
		}
	}
	else
	{
		if (! isset($xml_array ['name'])){
			for ($i = 0; $i < count ($xml_array); $i++){
				if (isset($xml_array [$i]['name']) && $xml_array [$i]['name'] == $first){
					$rezzz = rec_xml2array_getxpath ($xml_array [$i] , $xpath_expr_arr, $target_name);
					if (! $target_name)
					return $rezzz;
				}
			}
		}
		else{
			if ($xml_array ['name'] !== $first) return false;
			if (count ($xpath_expr_arr)){
				if (strpos($xpath_expr_arr[0] , '@') === false){
					if (! isset($xml_array ['elements']))
					return false;
					for ($i = 0; $i < count($xml_array ['elements']); $i++){
						$rezzz = rec_xml2array_getxpath ($xml_array ['elements'][$i] , $xpath_expr_arr, $target_name);
						if ($rezzz) {
							if (! $target_name)
							return $rezzz;
						}
					}
				}
				else
				return rec_xml2array_getxpath ($xml_array  , $xpath_expr_arr, $target_name);
			}
			else{
				if ($target_name)
				$GLOBALS[$target_name] [] = $xml_array;
				return $xml_array;
			}
		}
	}
	return false;
}
 
?>

И теперь пример исходника для javascript-файла который реализует userside (как всегда я использовал http://jquery.com/ ):

var outer_livechart_box = null;
 
$(document).ready(function() {
 outer_livechart_box = document.createElement('div');
 outer_livechart_box.id = 'outer_livechart_box';
 outer_livechart_box.className = 'outer_livechart_box';
 document.getElementsByTagName ('body')[0].appendChild (outer_livechart_box);
 
 outer_livechart_box.innerHTML  = 
  '<table border="0" class="tab_livechart_top_row">'+
  '<tr><td align="left" valign="top">'+
  '<div class="know_more_about_chartlive" ><a href="/mediawiki/index.php/google_chartlive">Узнай больше о проекте ChartLive</a></div>'+
  '</td><td align="right" valign="top">'+
  '<div id="clickable_chartlive_close_btn" class="clickable_chartlive_close_btn" onclick="toggle_chartlive()">Закрыть/Спрятать</div>'+
  '</td></tr></table>'+
  '<div id="inner_livechart_box">'+
  '<div>Выберите предпочтимый тип графика в списке: '+
  '<select name="sparkline" onchange="makeChartsLiveChange(this)">'+
	'<option value="VisitsSparkline">VisitsSparkline</option>'+
	'<option value="PageviewsSparkline">PageviewsSparkline</option>'+
	'<option value="AvgPageSparkline">AvgPageSparkline</option>'+
	'<option value="TimeOnSiteSparkline">TimeOnSiteSparkline</option>'+
	'<option value="BounceRateSparkline">BounceRateSparkline</option>'+
	'<option value="NewVisitsSparkline">NewVisitsSparkline</option>'+
	'<option value="AvgPageviewsSparkline">AvgPageviewsSparkline</option>'+
  '</select>'+
  '</div>'+
  '<img id="img_chart_zone" src="/mediawiki/extensions/chartvisits/google_pars.php?kind=image&sparkline=TimeOnSiteSparkline&w=500&h=200" />'+
  '</div>';
 
 
 
});
 
 
  function toggle_chartlive(){
    $('#outer_livechart_box').toggle();
  }  
  function makeChartsLiveChange(sel){
   $('#img_chart_zone').attr ({src: 
    '/mediawiki/extensions/chartvisits/google_pars.php?kind=image&sparkline='+sel.value+'&w=500&h=200&random='
+ Math.random()
   });
  }
  function make_toggle_big_chart (){
    $('#outer_livechart_box').toggle();		
    $('#outer_livechart_box').css ( {left: findPosX(document.getElementById('heading_line_visitors')) + 200, 
top: findPosY(document.getElementById('heading_line_visitors'))+20} );
  }
 
 
function findPosX(obj){ var curleft = 0; if (obj.offsetParent) { 
while (obj.offsetParent) { curleft += obj.offsetLeft; obj = obj.offsetParent; } } 
else if (obj.x) curleft += obj.x;  return curleft;}
function findPosY(obj){ var curtop = 0; if (obj.offsetParent) { 
while (obj.offsetParent) { curtop += obj.offsetTop; obj = obj.offsetParent; } } 
else if (obj.y)  curtop += obj.y; return curtop;}
 
 
function GetY(el){ var log = ''; var res = el.offsetTop; log += res + ', '; 
while(el = el.parentNode)	{if (! isNaN (el.offsetTop))   res += el.offsetTop; log += res + ', ';	}; 
return res; }
 
function GetX(el){ var res = el.offsetLeft; while(el = el.parentNode)	{
 if (! isNaN (el.offsetLeft))  res += el.offsetLeft;}
return res; }

И наконец, пример стилей css:

.outer_livechart_box {
 z-index: 1002;
 background-color: #cacaca;
 font-size: 12px; 
 padding: 3px; 
 margin: 3px; 
 border: 2px;
 position: absolute;
 left: 100px;
 top: 500px;
 display: none;
 width: 530px;
}
.clickable_chartlive_close_btn {
 cursor: pointer; 
 margin: 5px;
 padding-right: 25px;
 background:url(img/close_btn.png) no-repeat right top #cacaca; 
}
 
 
#img_chart_zone{
 margin: 5px;
 
}
 
.inner_livechart_box{
}
 
.know_more_about_chartlive{
}
 
.tab_livechart_top_row{
 background-color: #cacaca;
 width: 100%;
}

Subscribe Now!

ObMachine projects & articles (java, flash, flex, php, ...)  -- black-zorro.com


Личные инструменты
Навигация