} $arg0_values = $result; } if (!($arg0 && !$arg0_values)) { // we also don't want to display graph types that need arguments, but there aren't any if (isset($graph['category']) && $graph['category']) { echo "{ 'category' : " . json_encode($graph['title']) . " },\n"; } else { if (isset($graph['subcategory']) && $graph['subcategory']) { echo "{ 'subcategory' : " . json_encode($graph['title']) . " },\n"; } else { echo "{ 'id' : " . json_encode($id) . ", 'title' : " . json_encode($graph['title']) . ", 'description' : " . json_encode($graph['description']) . (isset($graph['technical']) && $graph['technical'] ? ", 'technical': true" : "") . (isset($graph['arg0_title']) && $graph['arg0_title'] ? ", 'arg0_title': " . json_encode($graph['arg0_title']) : "") . ($arg0 ? ", 'arg0': " . json_encode($arg0_values) : "") . ", 'string0': " . (isset($graph['string0']) && $graph['string0'] ? json_encode($graph['string0']) : "null") . ", 'days': " . json_encode(isset($graph['days'])) . ", 'delta': " . json_encode(isset($graph['delta'])) . "},\n"; } } } } } ?> ]; } function graph_technical_types() { return [ <?php foreach (graph_technical_types() as $id => $data) { echo "{ 'id' : '" . htmlspecialchars($id) . "', 'title' : '" . htmlspecialchars($data['title']) . "'" . ", 'description' : " . json_encode($data['description']) . ", 'premium' : " . ($data['premium'] ? "true" : "false") . ", 'period' : " . ($data['period'] ? "true" : "false") . "},\n"; } ?> ]; }
} ?> <tr> <th><?php echo t("Technical indicator types"); ?> </th> <td class="number"><?php echo number_format(count(graph_technical_types()) - $premium_technical_types); ?> (<?php echo implode(", ", $free_types); ?> )</td> <td class="number premium"><?php echo number_format(count(graph_technical_types())); ?> (<?php echo implode(", ", $premium_types); ?> )</td> </tr> <tr> <th><?php echo $welcome ? t("Your securities reports") : "<a href=\"" . htmlspecialchars(url_for('screenshots#screenshots_profile_summary')) . "\">" . ht("Your securities reports") . "</a>"; ?> </th> <?php foreach (array('free', 'premium') as $type) { ?> <td class="<?php
throw new Exception("Could not find graph '" . htmlspecialchars($graph_id) . "' for the current user"); } // we own this graph; edit it $q = db()->prepare("UPDATE graphs SET page_id=:page_id, graph_type=:graph_type, width=:width, height=:height, days=:days, delta=:delta, arg0=:arg0, string0=:string0 WHERE id=:id"); $q->execute(array('page_id' => $page_id, 'graph_type' => $graph_type, 'width' => $width, 'height' => $height, 'days' => $days, 'delta' => $delta, 'arg0' => $arg0, 'string0' => $string0, 'id' => $graph_id)); } else { $q = db()->prepare("INSERT INTO graphs SET page_id=:page_id, page_order=:page_order, graph_type=:graph_type, width=:width, height=:height, days=:days, delta=:delta, arg0=:arg0, string0=:string0"); $q->execute(array('page_id' => $page_id, 'page_order' => $new_order, 'graph_type' => $graph_type, 'width' => $width, 'height' => $height, 'days' => $days, 'delta' => $delta, 'arg0' => $arg0, 'string0' => $string0)); $graph_id = db()->lastInsertId(); } // technical graphs? $technical = require_post("technical", false); $technical_added = false; if ($technical) { // make sure that we don't add technicals that are premium only $graph_technical_types = graph_technical_types(); if (!isset($graph_technical_types[$technical])) { $errors[] = "Could not add technical type '" . htmlspecialchars($technical) . "' - no such technical type."; } else { if ($graph_technical_types[$technical]['premium'] && !$user['is_premium']) { $errors[] = "Could not add technical type '" . htmlspecialchars($graph_technical_types[$technical]['title']) . "' - requires a <a href=\"" . htmlspecialchars(url_for('premium')) . "\">premium account</a>."; } else { // it's OK // delete any existing technicals (even if we're inserting, since this logic is used for edit too) // (we limit a graph to only have a single technical at the moment) $q = db()->prepare("DELETE FROM graph_technicals WHERE graph_id=?"); $q->execute(array($graph_id)); // insert a new technical $q = db()->prepare("INSERT INTO graph_technicals SET graph_id=:graph_id, technical_type=:type, technical_period=:period"); $q->execute(array('graph_id' => $graph_id, 'type' => $technical, 'period' => min(get_site_config('technical_period_max'), max(1, (int) require_post("period", 0))))); $technical_added = htmlspecialchars($graph_technical_types[$technical]['title']);
/** * Apply graph technicals based on $graph['technicals'] - which is an array * of (technical_type, technical_period). * * @param $use_headings actual headings to use, or false (default) to load it from the $data; returns array(data, headings) instead of just data * @param $ignore_first_row if true (default), ignore the first row of rendered data */ function calculate_technicals($graph, $data, $use_headings = false, $ignore_first_row = true) { $days = get_graph_days($graph); if (!$use_headings) { $headings = $data[0]; if (count($data) <= 1) { throw new GraphException("Cannot calculate technicals for graph '" . htmlspecialchars($graph['graph_type']) . "' with no data"); } } else { $headings = $use_headings; if (count($data) <= 0) { throw new GraphException("Cannot calculate technicals for graph '" . htmlspecialchars($graph['graph_type']) . "' with no data"); } } $original_rows = count($headings); // need to sort data by date ksort($data); $new_headings = array(); if (isset($graph['technicals'])) { // if we have headings, then data that was in [1] and [2] are actually in [0] and [1]; // we fix the data here so we can continue to use old technical code silently // TODO this is a temporary fix until all code is using use_headings, and we can update all // other technicals code correctly if (!$ignore_first_row) { foreach ($data as $key => $row) { array_unshift($data[$key], "inserted temporary value"); } } $graph_technical_types = graph_technical_types(); foreach ($graph['technicals'] as $t) { $i = -1; foreach ($data as $label => $row) { $i++; if ($i == 0 && $ignore_first_row) { continue; } // skip heading row if ($i < count($data) - $days - 1) { continue; } // skip period data that isn't displayed // we now actually calculate data switch ($t['technical_type']) { case "sma": // simple moving average $new_headings = array(array('title' => "SMA (" . number_format($t['technical_period']) . ")", 'line_width' => 1, 'color' => default_technical_colour_index(), 'technical' => true)); $last = 0; $sum = 0; for ($j = 0; $j < $t['technical_period']; $j++) { $key = date('Y-m-d', strtotime($label . " -{$j} days")); $last = isset($data[$key]) ? isset($data[$key][2]) ? ($data[$key][1] + $data[$key][2]) / 2 : $data[$key][1] : $last; // take average if both bid and ask are defined $sum += $last; } $data[$label][] = graph_number_format($sum / $t['technical_period']); break; default: if (isset($graph_technical_types[$t['technical_type']]['callback'])) { // a premium graph technical type, defined elsewhere // should return array('headings' => array, 'data' => array) for each row $result = $graph_technical_types[$t['technical_type']]['callback']($graph, $t, $label, $data); $new_headings = $result['headings']; foreach ($result['data'] as $value) { $data[$label][] = $value; } break; } else { throw new GraphException("Unknown technical type '" . $t['technical_type'] . "'"); } } } } // TODO ... and then we get rid of the unnecessary labels if (!$ignore_first_row) { foreach ($data as $key => $row) { array_shift($data[$key]); } } // add headings foreach ($new_headings as $h) { if ($use_headings) { $headings[] = $h; } else { $data[0][] = $h; } } } if ($use_headings) { // (new behaviour, not implemented yet) return array('headings' => $headings, 'data' => $data); } else { // move the first $original_rows to the end, so they are displayed on top // (original behaviour) if (count($headings) != $original_rows) { $data_new = array(); foreach ($data as $label => $row) { $r = array(); $row_values = array_values($row); // get rid of any associative indexes $r[] = $row_values[0]; // keep date row for ($j = $original_rows; $j < count($row_values); $j++) { $r[] = $row_values[$j]; } for ($j = 1; $j < $original_rows; $j++) { $r[] = $row_values[$j]; } $data_new[$label] = $r; } $data = $data_new; } return $data; } }