I've been trying to create a chart similar to http://bit.ly/1jF6AY7 for a few weeks now using the Highcharts library. However, it does not understand that there should be zero values (if no value / date is provided) for the following series:
12-18-13
01-01-14
01-03-14
01-06-14
01-15-14
However, clearly using Google charts it is possible as the above graph is showing the gaps properly between the "contributed" dates. I need to do the same for my created_at dates. Any suggestions? I'd prefer to do this using Highcharts, but at this point my stress and hair is more valuable than anything else ;)
Vanilla PHP code example:
$q = $_GET['search'];
if(isset($_GET['time'])) {
$time = $_GET['time'];
} else {
$time = NULL;
}
include_once('../../vendors/HighchartsPHP-master/Highchart.php');
$chart = new Highchart(Highchart::HIGHSTOCK);
$chart->includeExtraScripts();
$chart->chart->renderTo = "chart";
$chart->title->text = "Potential Impact for \${$q}";
$chart->rangeSelector->selected = 1;
$chart->xAxis->type = 'datetime';
$chart->xAxis->gapGridLineWidth = 3;
$chart->xAxis->dateTimeLabelFormats->hour = "%H:%M";
$chart->xAxis->dateTimeLabelFormats->month = "%e. %b";
$chart->xAxis->dateTimeLabelFormats->year = "%b";
// $chart->xAxis->tickInterval = 3600 * 1000;
$chart->yAxis->title->text = "Volume";
$chart->yAxis->min = 0;
$chart->credits->enabled = false;
// $chart->rangeSelector->buttons[] = array(
// 'type' => "minute",
// 'count' => 5,
// 'text' => "5"
// );
...
$chart->rangeSelector->buttons[] = array(
'type' => "month",
'count' => 6,
'text' => "6M"
);
$chart->rangeSelector->buttons[] = array(
'type' => "year",
'count' => 1,
'text' => "1Y"
);
$chart->rangeSelector->buttons[] = array(
'type' => "all",
'text' => "All"
);
$option = new HighchartOption();
$option->global->useUTC = false;
Highchart::setOptions($option);
$chart->series[] = array(
'name' => "Potential Impact for \${$q}",
'data' => new HighchartJsExpr("data"),
'step' => 1,
'gapSize' => 5,
'connectNulls' => false,
'tooltip' => array(
'valueDecimals' => 0
)
);
$data = format_data($q, $time);
$chart->series[0]->data = $data;
?>
<div id="chart"></div>
<script type="text/javascript"><?php echo $chart->render("chart1"); ?></script>
And, the format data function:
function format_data($q, $time = NULL) {
$msg_vol = correlate_created_at_with_msg_vol($q, $time);
while ($tweet = mysqli_fetch_array($msg_vol, MYSQL_ASSOC)) {
$date = $tweet['date'] * 1000;
$count = intval($tweet['count']);
$formatted[] = array($date, $count);
}
return $formatted;
}
In the above picture, you can see not only is it graphing single data points (as illustrated by the tooltip) without showing any visual reference on the chart, but it is skipping the null periods, and not displaying the respective times properly on the xAxis. The odd part is that if you reference the bottom bar which allows the user to change the time period by dragging the bar... That bar has the gaps properly illustrated like what I'm looking for.
If I can't get Highcharts to go to a zero value for the gaps, I at the very least, need it to show the time intervals without jumping by an unpredictable number of minutes, hours, and in some cases, days and weeks.
If you use a datetime based series and there is no data for those points highcharts should handle it. Without seeing your code I image you are doing a category based xAxis and you are not "binning" any data in this time slot.
Edited:
Quick glance at your pasted code shows you are using HighStock, this is good. You can use the ordinal setting. Set this to false and you should see the gaps. Now it is still hard to say as I cannot see what your data is really but this should give us something to go on. You may also want to enable connectNulls.
Easy Solution, force 0 if there is no data.
Related
I'm trying to read the calendar entries of rooms and output the next three events on their calendar. However I am testing first to see if I can get just the first 3 events of the day, but it seems the timezone or something else is causing it to not show events correctly.
This is a function I wrote:
function mb_get_meetings($mb_email = null, $mb_proxy = false, $mb_datetime_start = null, $mb_datetime_finish = null)
{
date_default_timezone_set('Australia/Melbourne');
// get the Microsoft Open Graph API
$mb_msgraph = json_decode(mb_microsoft_opengraph($mb_proxy), true); // custom function to get Beaker Token + $mb_proxy for internal proxy on or off for dev testing
$mb_msgraph_token = $mb_msgraph['access_token'];
$mb_datetimenow = new DateTime();
$mb_datetimezone = new DateTimeZone('Australia/Melbourne');
$mb_datetimenow->setTimezone($mb_datetimezone);
$mb_datetime_start = new DateTime($mb_datetime_start, $mb_datetimezone);
$mb_datetime_finish = new DateTime($mb_datetime_finish, $mb_datetimezone);
$mb_datetime_start = $mb_datetime_start->format('Y-m-d\TH:i:s.u');
$mb_datetime_finish = $mb_datetime_finish->format('Y-m-d\TH:i:s.u');
$mb_url_string = 'https://graph.microsoft.com/v1.0/users/' . $mb_email . '/calendar/calendarView?startDateTime=' . $mb_datetime_start . '&endDateTime=' . $mb_datetime_finish;
$mb_args = array(
'headers' => array(
'Authorization' => 'Bearer ' . $mb_msgraph_token,
'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8',
'Prefer' => 'outlook.timezone="Australia/Melbourne"'
),
'httpversion' => '1.1'
);
$mb_output = wp_remote_get($mb_url_string, $mb_args);
$mb_output = wp_remote_retrieve_body($mb_output);
return $mb_output;
}
I am using Wordpress as the backend and it does retrieve the body.
In my frontend page I call:
$mbroom = (mb_get_meetings('email#domain.tld', true, 'today 7am', 'today 7pm'));
$mbroom = json_decode($mbroom, true);
$mbroom = $mbroom['value'];
foreach ($mbroom as $k => $v) {
// get the first 3 entries
if ($k < 3) {
print_r($k);
print_r($v['subject']);
print_r(date('g:i', strtotime($v['start']['dateTime']));
print_r(date('g:i', strtotime($v['end']['dateTime']));
print_r($v['organizer']['emailAddress']['name']);
echo '<hr>';
}
}
In the results, I will get sometimes no calendar entries while other times I might get entries for 2pm but not anything from 8am. I have tried changing it to hardcoded YYYY-MM-DDD 08:00 and YYYY-MM-DD 20:00 but to no avail. I have also tried yesterday and tomorrow but none/incorrect results.
I also tried booking an entire day with 30-minute meetings and that too wasn't working.
Am I doing something wrong?
I suggest sending the startDateTime and endDateTime parameters in UTC, formatted as ISO 8601 as described in the docs (https://learn.microsoft.com/en-us/graph/api/user-list-calendarview?view=graph-rest-1.0&tabs=http), which you have done. However, I'd suggest using a PHP constant, since it's less error-prone (https://www.php.net/manual/en/class.datetimeinterface.php#datetime.constants.iso8601). Doing something like the following for those parameters:
$startDateTime = new DateTime(date("Y-m-d H:i:s"));
$startDateTime = $startDateTime->format(DATE_ISO8601);
$startDateTime = substr($startDateTime, 0, strpos($startDateTime, '+'));
Edit:
Using substring to remove the locale part of the DateTime string was probably a naïve thing to do, but it does serve the purpose.
I have the following function:
function percentToColor($percent){
$minBrightness = 160;
$maxBrightness = 255;
// Remainins?
$brightness = ((($minBrightness-$maxBrightness)/(100-0))*$percent+$maxBrightness);
$first = (1-($percent/100))*$brightness;
$second = ($percent/100)*$brightness;
// Find the influence of the middle color (yellow if 1st and 2nd are red and green)
$diff = abs($first-$second);
$influence = ($brightness-$diff)/2;
$first = intval($first + $influence);
$second = intval($second + $influence);
// Convert to HEX, format and return
$firstHex = str_pad(dechex($first),2,0,STR_PAD_LEFT);
$secondHex = str_pad(dechex($second),2,0,STR_PAD_LEFT);
return $firstHex . $secondHex . '00';
}
This function accepts integers ranging from 0 to 100 and returns the color information for that number. Imagine a progress bar with red at 0 and green at 100. This is what the function does.
So I get the idea: if this function always returns the same color for each input (i.e. the colors aren't time/user/session dependent), the better idea would be to create a PHP array with the results, right?
So I rewrite the function:
function percentToColorNew($percent){
$results = array(
0 => 'ff0000',
1 => 'fe0500',
2 => 'fd0a00',
3 => 'fc0f00',
// ... continues with 4, 5, up until 100 ...
99 => '03a000',
100 => '00a000'
);
return $results[$percent];
}
And I test it. And the unexpected happens! The new function, which only returns the result from the array of results, takes double the time as the original function which has to compute the result every time it's called.
Why is this? Are PHP arrays this slow? Is there a faster way of storing the function results to speed up the function? A switch, maybe? If/elseif/else conditions? A different way of working with arrays?
It's slow because you are initializing the array each time you call the function.
Set up the array outside of function and it should be much faster. Quick and dirty example:
$results = array(
0 => 'ff0000',
1 => 'fe0500',
2 => 'fd0a00',
3 => 'fc0f00',
// ... continues with 4, 5, up until 100 ...
99 => '03a000',
100 => '00a000'
);
function percentToColorNew($percent){
global $results;
return $results[$percent];
}
Edit: Or even better use the static keyword:
function percentToColorNew($percent){
static $results;
if(!is_array($results)) {
$results = array ( ... );
}
return $results[$percent];
}
I am trying to count how many hits I have on my site each hour, but am not sure how to approach this.
Here is what i have now:
if($cacheAvailable == true){ // WE GOT A CACHE
date_default_timezone_set("UTC");
$thisHour = date("H", time());
$moveStats = $memcache->get('moveStats');
if(!$moveStats){
$todayStats = array(array(hour => $thisHour, hits => 1, executetime => $total_time));
$memcache->set('moveStats', $todayStats);
}
foreach ($moveStats as $k => $v) {
if($v['hour'] == $thisHour){
$moveStats[$k]['hits']=$moveStats[$k]['hits']+1;
}
}
$memcache->set('moveStats', $moveStats);
echo '<pre>';
print_r($moveStats);
echo '</pre>';
}
This makes an array like this:
Array
(
[0] => Array
(
[hour] => 18
[hits] => 6
[executetime] => 0
)
)
//##### EDIT ######//
I am able to add to the current hour but I don't know how to add a new hour when the clock turns into the new hour?
Hoping for help and thanks in advance.
You just have to check if that index already exists, if not create a new one, and always increase the old value:
$todayStats = $moveStats;
if (!isset($todayStats [$thisHour])) {
$todayStats[$thisHour] = 0;
}
$todayStats[$thisHour]['hits']++;
$todayStats[$thisHour]['executetime'] = $total_time;
But you have some other problems in your implementation:
- Don't use string without quotes. That will try to call a constant with that name and only as fallback return the string itself. It also raises a notice.
- $thisHour won't contain the current hour. If you really want to have the hour try: date('H') only.
For example, I have a user, and the user have different user right, for example, a user can have
-create file
-read file
-update file
-delete file
4 rights, I can use 4 BOOL to find the user right, but if the user have more right, I need create more and more BOOL to store the right. I don't think it is a good idea. And I think of getting a long integer for this... for example, the user can do all the stuff is 1111.
create file is 1000, read file is 100, update is 10, delete is 1. So, if the user only get read file right is 0100.
Is there any better ideas?? Thank you.
I suggest to convert privileges (rights) in binary-state-string and later, store it in database as long integer or hexadecimal string (VARCHAR; for storing a lot of rights).
Example
$privileges_list = array(
0 => 'create_file',
1 => 'read_file',
2 => 'update_file',
3 => 'delete_file',
4 => 'create_pool',
5 => 'vote_in_pool',
6 => 'create_gallery',
7 => 'upload_images',
8 => 'view_statistics'
);
So if you want to set create file, update file and view statistics rights to the user just put 1 on appropriate positions in string (0, 2, 8) and 0 for rest of them
$binary_string = "100000101";
Last character in this string is position 0, first is position 8.
Now you can convert this string to integer (261) or hex-number (105) and put it into database as privilege-set for that user (I prefer hex).
To convert this value back to privileges-list you can use something like this
function hexvalue2privileges($hexvalue, $plist) {
$res = array(); $res_assoc = array();
for ($i = strlen($hexvalue) - 1; $i >= 0; $i--) {
$bin = str_pad(decbin(hexdec(substr($hexvalue, $i, 1))), 4, '0', STR_PAD_LEFT);
$bin_array = array_reverse(str_split($bin, 1));
foreach ($bin_array as $bitstate) $res[] = $bitstate == '1' ? true : false;
}
foreach ($plist as $key => $id) {
$res_assoc[$id] = $res[$key];
}
return $res_assoc;
}
and call this function
print_r(hexvalue2privileges('105', $privileges_list));
Output will be
Array
(
[create_file] => 1 // true
[read_file] => // false
[update_file] => 1 // true
[delete_file] => // false
[create_pool] => // false
[vote_in_pool] => // false
[create_gallery] => // false
[upload_images] => // false
[view_statistics] => 1 // true
)
With every hexadecimal character you can store 4 rights so to calculate number of character needed use this formula
$chars_needed = floor((count($privileges_list)-1) / 4) + 1; // result 3
To get total length of binary-string
$binary_length = $chars_needed * 4; // result 12
To fix length of privileges set
$binary_string = "100000101";
$binary_string = str_pad($binary_string, $binary_length, '0', STR_PAD_LEFT);
// result '000100000101'
To convert $binary_string to hex
$binary_string = "000100000101";
$hexvalue = "";
$groups = str_split($binary_string, 4);
foreach ($groups as $group) $hexvalue .= dechex(bindec($group));
// result $hexvalue='105' (1=0001, 0=0000, 5=0101)
Also you can create groups of privileges and assign them to users by creating privileges-set for every group (administrators, moderators, guests, vip, etc).
Hope this helps
Use [flags CHAR(10) default '0000000000']
Whenever you need any digit, you can use it like
1st digit - New
2nd digit - Edit etc.
At the time of storing, you just need to change that bit and store like this 1100000000. That's it.
What would be the best, simplest way to code this:
I have a php document that gets most of the page requests (set up in routes config, using code igniter framework) and depending on the uri i want to show the user different content. Example:
http: // domain.tld/2010/
Should show one type of content
http: // domain.tld/2010-nov/
should show another type of content
http: // domain.tld/2010-nov-10/
should show yet another type of content
http: // domain.tld/2010-nov-10-blog-post-title/
should show once again another type of content
Everything else should be treated as if is a product, example:
http: // domain.tld/light-bulb/
and if such a product doesnt exist, its a 404
Below is the code I got at the moment, but I feel it is way too messy. Any suggestion how to make it simpler and more effective? (Tried to get it formatted correctly here but it seem a bit tricky to get code to align properly, a apologize)
Regards,
Jason
(had to add spaces in all my urls here because im new and not allowed to post that many urls)
$val is the uri (/2010-nov.......)
function show_what($val){
$arr=array("jan"=>01,"feb"=>02,"mar"=>03,"apr"=>04,"may"=>05,"jun"=>06,"jul"=>07,"aug"=>08,"sep"=>09,"oct"=>10,"nov"=>11,"dec"=>12);
// first check to see if the uri starts with a year (4 digits)
if(is_int((int)substr($val,0,4)) && (int)substr($val,0,4)!=0){
// Get all posts for specified YEAR
if(strlen($val)==4){
// Show all blog posts for specified year
// example: http: // domain.tld/2010/
// Get all posts for specified YEAR and MONTH
}elseif(strlen($val)==8 && substr($val,4,1)=="-" && array_key_exists(substr($val,5,3),$arr)){
// show all blog posts for specified year and month
// example: http: // domain.tld/2010-nov/
// Get all posts for specified YEAR, MONTH and DAY OR! get specified post
}elseif(strlen($val)>=11 && substr($val,4,1)=="-" && array_key_exists(substr($val,5,3),$arr) && substr($val,8,1)=="-" && is_int((int)substr($val,9,2)) && (int)substr($val,9,2)!=0){
// Get all posts for specified YEAR, MONTH and DAY
if(strlen($val)==11){
// show all blog posts for specified year, month and day
// example: http: // domain.tld/2010-nov-10/
// Get specified post
}elseif(substr($val,11,1)=="-"){
// show specified post or 404
// example: http: // domain.tld/2010-nov-10-blog-post-title/
}else{
// "Not a valid article url<Br/>";
// example: http: // domain.tld/2010-nov-10there-is-a-dash-missing-after-day/
}
}else{
// 404, not a real date
}
}else{
// show product with current uri or if it doesnt exist, 404.
}
}
I'm not a PHP guy and wouldn't actually know how to implement it on PHP but you should definitely look for URL Rewrite with mod_rewrite in Apache or URL Rewrite in IIS 7 and take advantage of Regular Expressions so you don't need to parse strings.
You can use regular expressions to parse the URL part. For example:
(?<Year>[0-9]{4})
(-
(?<Month>[a-zA-Z]+)
(-
(?<Day>[0-9]{1,2})
(-
(?<Slugg>.*)
)?
)?
)?
(Almost reminds you of Lisp, doesn't it?)
Depending upon which parts are present and valid, perform the appropriate logic.
Here's a tested example to get you started. It includes my regexp solution and a solution using string splitting as suggested by others.
<?php
function getParts($source) {
$re = '/^
(?<Year>[0-9]{4})
(-
(?<Month>[a-zA-Z]+)
(-
(?<Day>[0-9]{1,2})
(-
(?<Slugg>.*)
)?
)?
)?
$/';
$re = str_replace(array(' ', "\n", "\r", "\t"), '', $re); // Strip whitespace that made the RE readable
$matches = null;
if (!preg_match($re, $source, $matches)) {
return array('title' => $source);
}
$ret = array();
if (!$matches['Year']) {
return $ret;
}
$ret['year'] = (int) $matches['Year'];
if (!$matches['Month']) {
return $ret;
}
$monthToNumber = array('jan' => 1, 'feb' => 2, 'mar' => 3, 'apr' => 4, 'may' => 5, 'jun' => 6, 'jul' => 7, 'aug' => 8, 'sep' => 9, 'oct' => 10, 'nov' => 11, 'dec' =>>
$monthName = strtolower($matches['Month']);
if (!array_key_exists($monthName, $monthToNumber)) {
return $ret;
}
$ret['month'] = $monthToNumber[$monthName];
if (!$matches['Day']) {
return $ret;
}
$ret['day'] = (int) $matches['Day'];
if (!$matches['Slugg']) {
return $ret;
}
$ret['title'] = $matches['Slugg'];
return $ret;
}
function getParts2($source) {
$ret = array();
$errorRet = array('title' => $source);
$rawParts = explode('-', $source, 4);
if (count($rawParts) < 1 || !is_numeric($rawParts[0])) {
return $errorRet;
}
$ret['year'] = (int) $rawParts[0];
if (count($rawParts) < 2) {
return $ret;
}
$monthToNumber = array('jan' => 1, 'feb' => 2, 'mar' => 3, 'apr' => 4, 'may' => 5, 'jun' => 6, 'jul' => 7, 'aug' => 8, 'sep' => 9, 'oct' => 10, 'nov' => 11, 'dec' =>>
$monthName = strtolower($rawParts[1]);
if (!array_key_exists($monthName, $monthToNumber)) {
return $errorRet;
}
$ret['month'] = $monthToNumber[$monthName];
if (count($rawParts) < 3) {
return $ret;
}
$ret['day'] = (int) $rawParts[2];
if (count($rawParts) < 4) {
return $ret;
}
$ret['title'] = $rawParts[3];
return $ret;
}
function test($testFunc, $source, $expected) {
$actual = call_user_func($testFunc, $source);
if ($actual !== $expected) {
echo "Test failed;\n";
echo "Input: ";
var_dump($source);
echo "Actual: ";
var_dump($actual);
echo "Expected: ";
var_dump($expected);
}
}
foreach (array('getParts', 'getParts2') as $testFunc) {
test($testFunc, '2010', array('year' => 2010));
test($testFunc, '2010-nov', array('year' => 2010, 'month' => 11));
test($testFunc, '2010-nov-10', array('year' => 2010, 'month' => 11, 'day' => 10));
test($testFunc, '2010-nov-10-blog-post-title', array('year' => 2010, 'month' => 11, 'day' => 10, 'title' => 'blog-post-title'));
test($testFunc, 'light-bulb', array('title' => 'light-bulb'));
}
you can simple explode it to array
$array = explode('-',$val);
and make an switch case of the array size like
switch(count($array){
# is like 2010
case 1:
// Show all blog posts for specified year
// example: http: // domain.tld/2010/
$year = $array[0];
break;
.....
}
Split the part on hyphens, like this:
$parts = explode('-', $url)
if (count($parts) == 2) {
$year = $parts[0];
$month = $parts[1];
} else if (count($parts) == 3) {
etc.
You should really take a look at the CodeIgniter Framework. Things like URL ReWrite and such are all build into the Framework and it's easy to use + lightning fast. I'm using it since 2008.