function api_v1_graphs($graph) { $start_time = microtime(true); $result = array(); /** * Graph rendering goes like this: * 0. check graph rendering permissions * 1. get raw graph data (from a {@link GraphRenderer} through {@link construct_graph_renderer()}) * 2. apply deltas as necessary * 3. add technicals as necessary * 4. strip dates outside of the requested ?days parameter (e.g. from extra_days) * 5. construct heading and links * 6. construct subheading and revise last_updated * 7. return data * that is, deltas and technicals are done on the server-side; not the client-side. */ $renderer = construct_graph_renderer($graph['graph_type'], $graph['arg0'], $graph['arg0_resolved']); // 0. check graph rendering permissions if ($renderer->requiresUser()) { if (!isset($graph['user_id']) || !$graph['user_id']) { throw new GraphException("No user specified for authenticated graph"); } if (!isset($graph['user_hash']) || !$graph['user_hash']) { throw new GraphException("No user hash specified for authenticated graph"); } $user = get_user($graph['user_id']); if (!$user) { throw new GraphException("No such user found"); } if (!has_expected_user_graph_hash($graph['user_hash'], $user)) { throw new GraphException("Mismatched user hash for user " . $graph['user_id'] . " with graph type " . $graph['graph_type']); } if ($renderer->requiresAdmin()) { if (!$user['is_admin']) { throw new GraphException("Graph requires administrator privileges"); } } $renderer->setUser($user['id']); } if ($renderer->usesDays()) { // 0.5 limit 'days' parameter as necessary $get_permitted_days = get_permitted_days(); $has_valid_days = false; foreach ($get_permitted_days as $key => $days) { if ($days['days'] == $graph['days']) { $has_valid_days = true; } } if (!$has_valid_days) { throw new GraphException("Invalid days '" . $graph['days'] . "' for graph that requires days"); } } // 1. get raw graph data try { $data = $renderer->getData($graph['days']); $original_count = count($data['data']); $result['type'] = $renderer->getChartType(); // 2. apply deltas as necessary $data['data'] = calculate_graph_deltas($graph, $data['data'], false); // if there is no data, bail out early if (count($data['data']) == 0) { $result['type'] = 'nodata'; } else { if ($renderer->canHaveTechnicals()) { // 3. add technicals as necessary // (only if there is at least one point of data, otherwise calculate_technicals() will throw an error) $technicals = calculate_technicals($graph, $data['data'], $data['columns'], false); $data['columns'] = $technicals['headings']; $data['data'] = $technicals['data']; } } // 4. discard early data if ($renderer->usesDays()) { $data['data'] = discard_early_data($data['data'], $graph['days']); $after_discard_count = count($data['data']); } $result['columns'] = $data['columns']; $result['key'] = $data['key']; $result['data'] = $data['data']; // clean up columns foreach ($result['columns'] as $key => $value) { $result['columns'][$key]['technical'] = isset($result['columns'][$key]['technical']) && $result['columns'][$key]['technical'] ? true : false; if ($result['columns'][$key]['technical']) { if (!isset($result['columns'][$key]['type'])) { $result['columns'][$key]['type'] = 'number'; } } } } catch (NoDataGraphException_AddAccountsAddresses $e) { $result['type'] = 'nodata'; $result['text'] = ct("Either you have not specified any accounts or addresses, or these addresses and accounts have not yet been updated by :site_name."); $result['args'] = array(':site_name' => get_site_config('site_name')); $result['data'] = array(); $data['last_updated'] = false; $data['add_accounts_addresses'] = true; } catch (NoDataGraphException_AddCurrencies $e) { $result['type'] = 'nodata'; $result['text'] = ct("Either you have not enabled this currency, or your summaries for this currency have not yet been updated by :site_name."); $result['args'] = array(':site_name' => get_site_config('site_name')); $result['data'] = array(); $data['last_updated'] = false; $data['add_more_currencies'] = true; } // 5. construct heading and links $result['heading'] = array('label' => $renderer->getTitle(), 'args' => $renderer->getTitleArgs(), 'url' => $renderer->getURL(), 'title' => $renderer->getLabel()); if (isset($data['h1'])) { $result['h1'] = $data['h1']; } if (isset($data['h2'])) { $result['h2'] = $data['h2']; } if (isset($data['no_header'])) { $result['noHeader'] = $data['no_header']; } // 6. construct subheading and revise last_updated\ if ($result['type'] != 'nodata' && $renderer->hasSubheading()) { $suffix = ""; if ($graph['delta'] == 'percent') { $suffix .= '%'; } if ($renderer->getCustomSubheading() !== false) { $result['subheading'] = number_format_html($renderer->getCustomSubheading(), 4, $suffix); } else { if ($result['type'] == 'piechart') { // sum up the first row and use that as a total if (count($data['data']) != 1) { throw new GraphException("Expected one row of data for a piechart, got " . count($data['data'])); } $sum = 0; foreach ($data['data'] as $ignored => $row) { foreach ($row as $value) { $sum += $value; } } $result['subheading'] = number_format_html($sum, 4, $suffix); } else { $result['subheading'] = format_subheading_values_objects($graph, $data['data'], $data['columns']); } } } $result['lastUpdated'] = recent_format_html($data['last_updated']); $result['timestamp'] = iso_date(); $result['classes'] = $renderer->getClasses(); $result['graph_type'] = $graph['graph_type']; if (is_localhost()) { $result['_debug'] = $graph; if (isset($after_discard_count)) { $result['_debug']['data_discarded'] = $original_count - $after_discard_count; } else { $result['_debug']['data_not_discarded'] = true; } } // make sure that all 'number'-typed data is numeric foreach ($result['data'] as $i => $row) { foreach ($row as $key => $value) { $column = $result['columns'][$key]; if ($column['type'] == 'number' || $column['type'] == 'percent') { $result['data'][$i][$key] = (double) $value; if (is_localhost()) { $result['_debug']['number_formatted'] = true; } } } } // make sure that all data rows are numeric arrays and not objects // i.e. reindex everything to be numeric arrays, so they aren't output as JSON objects foreach ($result['data'] as $i => $row) { $new_row = array_values($row); foreach ($row as $key => $value) { $new_row[$key] = $value; } $result['data'][$i] = $new_row; } // format any extra text from the result if (isset($data['add_more_currencies'])) { $result['extra'] = array('classes' => 'add_accounts', 'href' => url_for('wizard_currencies'), 'label' => ct("Add more currencies"), 'args' => array()); } if (isset($data['add_accounts_addresses'])) { $result['extra'] = array('classes' => 'add_accounts', 'href' => url_for('wizard_accounts'), 'label' => ct("Add accounts and addresses"), 'args' => array()); } // 7. calculate if the graph data may be out of date if ($renderer->requiresUser() && $renderer->getUser()) { $user = get_user($renderer->getUser()); if ($user && $renderer->usesSummaries() && (!$user['has_added_account'] || !$user['is_first_report_sent'] || strtotime($user['last_account_change']) > strtotime($user['last_sum_job']))) { $result['outofdate'] = true; } } $end_time = microtime(true); $time_diff = ($end_time - $start_time) * 1000; $result['time'] = (double) number_format_autoprecision($time_diff, 1, '.', ''); $result['hash'] = $graph['hash']; // 7. return data return $result; }
?> MH/s</td> </tr> <?php $first_tab = false; } ?> </tbody> <tfoot> <tr> <th colspan="2"><?php echo t("Total :currency", array(':currency' => get_currency_name($currency))); ?> ></th> <th class="number"><?php echo number_format_html($sum, 4); ?> MH/s</th> </tr> </tfoot> </table> </li> <?php } ?> <?php if (!$balances) { ?> <li>
public function getData($days) { $key_column = array('type' => 'string', 'title' => ct("Currency")); $columns = array(); $last_updated = false; $columns[] = array('type' => 'string', 'title' => "", 'heading' => true); // a matrix table of each currency vs. each currency, and their current // last_trade and volume on each exchange the user is interested in $currencies = get_all_currencies(); $summaries = get_all_summary_currencies($this->getUser()); $conversion = get_all_conversion_currencies($this->getUser()); $graph["last_updated"] = 0; $interested = array(); foreach ($currencies as $c) { if (isset($summaries[$c])) { $interested[] = $c; $columns[] = array('type' => 'string', 'title' => get_currency_abbr($c)); } } foreach ($interested as $c1) { $row = array(get_currency_abbr($c1)); foreach ($interested as $c2) { // go through each exchange pair $cell = ""; foreach (get_exchange_pairs() as $exchange => $pairs) { foreach ($pairs as $pair) { if ($c1 == $pair[0] && $c2 == $pair[1]) { $q = db()->prepare("SELECT * FROM ticker_recent WHERE exchange=? AND currency1=? AND currency2=? LIMIT 1"); $q->execute(array($exchange, $c1, $c2)); if ($ticker = $q->fetch()) { // TODO currency_format should be a graph option $exchange_short = strlen($exchange) > 8 ? substr($exchange, 0, 7) . "..." : $exchange; $cell .= "<li><span class=\"rate\">" . number_format_html($ticker['last_trade'], 4) . "</span> " . ($ticker['volume'] == 0 ? "" : "<span class=\"volume\">(" . number_format_html($ticker['volume'], 4) . ")</span>"); $cell .= " <span class=\"exchange\" title=\"" . htmlspecialchars(get_exchange_name($exchange)) . "\">[" . htmlspecialchars($exchange_short) . "]</span>"; $cell .= "</li>\n"; $last_updated = max($last_updated, strtotime($ticker['created_at'])); } else { $cell .= "<li class=\"warning\">" . t("Could not find rate for :exchange: :pair", array(':exchange' => $exchange, ':pair' => $c1 . "/" . $c2)) . "</li>\n"; } } } } if ($cell) { $cell = "<ul class=\"rate_matrix\">" . $cell . "</ul>"; } $row[] = $cell; } $data[] = $row; } // now delete any empty rows or columns // columns $deleteRows = array(); $deleteColumns = array(); for ($i = 0; $i < count($data) - 1; $i++) { $empty = true; for ($j = 1; $j < count($data[$i]); $j++) { if ($data[$i][$j]) { $empty = false; break; } } if ($empty) { $deleteRows[] = $i; } } for ($i = 1; $i < count($data); $i++) { $empty = true; for ($j = 0; $j < count($data[$i]) - 1; $j++) { if ($data[$j][$i]) { $empty = false; break; } } if ($empty) { $deleteColumns[] = $i; } } $new_data = array(); foreach ($data as $i => $row) { if (in_array($i, $deleteRows)) { continue; } $x = array(); foreach ($data[$i] as $j => $cell) { if (in_array($j, $deleteColumns)) { continue; } $x[] = $cell; } $new_data[] = $x; } foreach ($deleteColumns as $i) { unset($columns[$i]); } $columns = array_values($columns); return array('key' => $key_column, 'columns' => $columns, 'data' => $new_data, 'last_updated' => $last_updated, 'add_more_currencies' => true); }
/** * Same as format_subheading_values(), but sum all values together. */ function format_subheading_values_subtotal($graph, $input, $suffix = false) { $array = array_slice($input, 1, 1, true); $array = array_pop($array); // array_slice returns an array(array(...)) // array[0] is always the date; the remaining values are the formatted data // remove any data that is a Date heading or a technical value foreach ($input[0] as $key => $heading) { if ($key === 0 || is_array($heading) && isset($heading['technical']) && $heading['technical']) { unset($array[$key]); } } if (!$array) { return ""; } $total = 0; foreach ($array as $key => $value) { $total += $value; } if ($graph['delta'] == 'percent') { $suffix .= '%'; } return number_format_html($total, 4, $suffix); }