diff --git a/moje-statystyki.php b/moje-statystyki.php index 9b03c08..81f53d0 100644 --- a/moje-statystyki.php +++ b/moje-statystyki.php @@ -431,14 +431,16 @@ function mystat_yearly_summary_page() { SELECT MONTH(date) as month_num, SUM(distance) as total_distance, - SUM(calories) as total_calories, - SEC_TO_TIME(SUM(TIME_TO_SEC(duration))) as total_duration + SUM(calories) as total_calories, + SUM(TIME_TO_SEC(duration)) as total_seconds, + COUNT(id) as activity_count FROM $table_activities WHERE YEAR(date) = %d GROUP BY month_num ORDER BY month_num ASC ", $current_year); + $monthly_summary = $wpdb->get_results( $sql, OBJECT_K ); // OBJECT_K zwróci tablicę z kluczami będącymi numerami miesięcy // Przygotowanie danych dla wszystkich 12 miesięcy @@ -468,16 +470,14 @@ function mystat_yearly_summary_page() { $full_year_summary[$i] = (object) [ 'month_name' => $month_name, 'total_distance' => $data ? $data->total_distance : 0, - 'total_calories' => $data ? $data->total_calories : 0, - 'total_duration' => $data ? $data->total_duration : '00:00:00', + 'total_calories' => $data ? (int)$data->total_calories : 0, + 'total_seconds' => $data ? (int)$data->total_seconds : 0, + 'activity_count' => $data ? (int)$data->activity_count : 0, ]; $total_year_distance += $full_year_summary[$i]->total_distance; + $total_year_seconds += $full_year_summary[$i]->total_seconds; $total_year_calories += $full_year_summary[$i]->total_calories; - if ( $data && ! empty( $data->total_duration ) ) { - list($h, $m, $s) = explode(':', $data->total_duration); - $total_year_seconds += $h * 3600 + $m * 60 + $s; - } } $total_year_hours = floor($total_year_seconds / 3600); @@ -485,44 +485,105 @@ function mystat_yearly_summary_page() { $total_year_duration_formatted = sprintf('%d godz. %d min.', $total_year_hours, $total_year_minutes); // Przygotowanie danych dla wykresu - $chart_labels = []; - $chart_data_distance = []; + $chart_labels_js = []; + $chart_datasets = [ + 'distance' => [], + 'duration' => [], + 'calories' => [], + 'activities' => [], + ]; + foreach ($full_year_summary as $month_data) { - $chart_labels[] = $month_data->month_name; - $chart_data_distance[] = round((float)$month_data->total_distance, 2); + $chart_labels_js[] = $month_data->month_name; + $chart_datasets['distance'][] = round((float)$month_data->total_distance, 2); + $chart_datasets['duration'][] = round($month_data->total_seconds / 3600, 2); // w godzinach + $chart_datasets['calories'][] = $month_data->total_calories; + $chart_datasets['activities'][] = $month_data->activity_count; } // Włączenie skryptów dla Chart.js wp_enqueue_script('chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', [], null, true); wp_register_script('mystat-chart-loader', false); wp_enqueue_script('mystat-chart-loader'); + + $chart_configs = [ + 'distance' => [ + 'label' => 'Dystans (km)', + 'data' => $chart_datasets['distance'], + 'backgroundColor' => 'rgba(52, 152, 219, 0.5)', + 'borderColor' => 'rgba(52, 152, 219, 1)', + 'yAxisLabel' => 'Kilometry' + ], + 'duration' => [ + 'label' => 'Czas trwania (godz.)', + 'data' => $chart_datasets['duration'], + 'backgroundColor' => 'rgba(26, 188, 156, 0.5)', + 'borderColor' => 'rgba(26, 188, 156, 1)', + 'yAxisLabel' => 'Godziny' + ], + 'calories' => [ + 'label' => 'Kalorie (kcal)', + 'data' => $chart_datasets['calories'], + 'backgroundColor' => 'rgba(231, 76, 60, 0.5)', + 'borderColor' => 'rgba(231, 76, 60, 1)', + 'yAxisLabel' => 'kcal' + ], + 'activities' => [ + 'label' => 'Liczba aktywności', + 'data' => $chart_datasets['activities'], + 'backgroundColor' => 'rgba(241, 196, 15, 0.5)', + 'borderColor' => 'rgba(241, 196, 15, 1)', + 'yAxisLabel' => 'Ilość' + ], + ]; + wp_add_inline_script('mystat-chart-loader', ' document.addEventListener("DOMContentLoaded", function() { - const ctx = document.getElementById("mystatYearlyChart"); - if (!ctx) return; - new Chart(ctx, { - type: "bar", - data: { - labels: ' . json_encode($chart_labels) . ', - datasets: [{ - label: "Dystans (km)", - data: ' . json_encode($chart_data_distance) . ', - backgroundColor: "rgba(52, 152, 219, 0.5)", - borderColor: "rgba(52, 152, 219, 1)", - borderWidth: 1 - }] - }, - options: { - responsive: true, - maintainAspectRatio: false, - scales: { - y: { - beginAtZero: true, - title: { display: true, text: "Kilometry" } - } - } - } + const chartLabels = ' . json_encode($chart_labels_js) . '; + const chartConfigs = ' . json_encode($chart_configs) . '; + let activeChart = null; + + const tabs = document.querySelectorAll(".nav-tab"); + tabs.forEach(tab => { + tab.addEventListener("click", function(e) { + e.preventDefault(); + tabs.forEach(t => t.classList.remove("nav-tab-active")); + this.classList.add("nav-tab-active"); + + const chartType = this.getAttribute("href").substring(1); + renderChart(chartType); + }); }); + + function renderChart(type) { + if (activeChart) { + activeChart.destroy(); + } + const config = chartConfigs[type]; + const ctx = document.getElementById("mystatYearlyChart").getContext("2d"); + activeChart = new Chart(ctx, { + type: "bar", + data: { + labels: chartLabels, + datasets: [{ + label: config.label, + data: config.data, + backgroundColor: config.backgroundColor, + borderColor: config.borderColor, + borderWidth: 1 + }] + }, + options: { + responsive: true, maintainAspectRatio: false, + scales: { y: { beginAtZero: true, title: { display: true, text: config.yAxisLabel } } } + } + }); + } + + // Render initial chart + if (tabs.length > 0) { + renderChart(tabs[0].getAttribute("href").substring(1)); + } }); '); @@ -546,8 +607,14 @@ function mystat_yearly_summary_page() {
-

Wykres Dystansu w

+

Wykresy dla

+
@@ -564,7 +631,7 @@ function mystat_yearly_summary_page() { month_name ); ?> total_distance, 2, ',', ' ' ); ?> total_calories, 0, ',', ' ' ); ?> - total_duration ); ?> + total_seconds) ); ?> @@ -580,12 +647,12 @@ function mystat_yearly_summary_page() { } /** - * Fetches and parses a GPX file from a URL to extract track points. + * Fetches and parses a GPX file from a URL to extract track points and elevation profile. * * @param string $gpx_url The URL of the GPX file. - * @return array An array of [lat, lon] coordinates, or an empty array on failure. + * @return array An array containing 'points' for the map and 'elevation_profile'. */ -function mystat_get_track_from_gpx_url( $gpx_url ) { +function mystat_parse_gpx_data( $gpx_url ) { if ( empty( $gpx_url ) ) { return []; } @@ -609,18 +676,68 @@ function mystat_get_track_from_gpx_url( $gpx_url ) { return []; } - $points = []; + $track_data = [ + 'points' => [], + 'elevation_profile' => [], + ]; + $raw_points = []; + + // Extract raw points with lat, lon, ele if ( isset( $gpx->trk ) ) { foreach ( $gpx->trk->trkseg as $trkseg ) { foreach ( $trkseg->trkpt as $trkpt ) { - if ( isset( $trkpt['lat'] ) && isset( $trkpt['lon'] ) ) { - $points[] = [ (float) $trkpt['lat'], (float) $trkpt['lon'] ]; + if ( isset( $trkpt['lat'], $trkpt['lon'] ) ) { + $raw_points[] = [ + 'lat' => (float) $trkpt['lat'], + 'lon' => (float) $trkpt['lon'], + 'ele' => isset( $trkpt->ele ) ? (float) $trkpt->ele : null, + ]; } } } } - return $points; + if ( empty( $raw_points ) ) { + return $track_data; + } + + // Process raw points to calculate profile + $cumulative_distance = 0; + $elevation_profile = []; + $map_points = []; + + $haversine = function( $lat1, $lon1, $lat2, $lon2 ) { + $earth_radius = 6371; // in km + $dLat = deg2rad( $lat2 - $lat1 ); + $dLon = deg2rad( $lon2 - $lon1 ); + $a = sin( $dLat / 2 ) * sin( $dLat / 2 ) + + cos( deg2rad( $lat1 ) ) * cos( deg2rad( $lat2 ) ) * + sin( $dLon / 2 ) * sin( $dLon / 2 ); + $c = 2 * atan2( sqrt( $a ), sqrt( 1 - $a ) ); + return $earth_radius * $c; + }; + + foreach ( $raw_points as $i => $point ) { + $map_points[] = [ $point['lat'], $point['lon'] ]; + + if ( $i > 0 ) { + $prev_point = $raw_points[ $i - 1 ]; + $distance_increment = $haversine( $prev_point['lat'], $prev_point['lon'], $point['lat'], $point['lon'] ); + $cumulative_distance += $distance_increment; + } + + if ( ! is_null( $point['ele'] ) ) { + $elevation_profile[] = [ + 'distance' => round( $cumulative_distance, 3 ), // in km + 'elevation' => round( $point['ele'], 2 ), // in m + ]; + } + } + + $track_data['points'] = $map_points; + $track_data['elevation_profile'] = $elevation_profile; + + return $track_data; } function mystat_infographic_page() { @@ -1123,35 +1240,68 @@ function mystat_view_activity_page() { } }; - // Prepare map data if GPX exists - $track_points = []; + // Prepare map and chart data if GPX exists + $gpx_data = []; if ( ! empty( $activity->gpx_url ) ) { - // Note: For performance, you might want to cache the parsed points in post meta - // rather than parsing the file on every page load. - $track_points = mystat_get_track_from_gpx_url( $activity->gpx_url ); + $gpx_data = mystat_parse_gpx_data( $activity->gpx_url ); - if ( ! empty( $track_points ) ) { - // Enqueue Leaflet assets. For simplicity, this is done here. - // A better approach is to use the 'admin_enqueue_scripts' hook. + if ( ! empty( $gpx_data['points'] ) ) { wp_enqueue_style('leaflet-css', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css'); wp_enqueue_script('leaflet-js', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', [], '1.9.4', true); + wp_enqueue_script('chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', [], null, true); - // Pass track data to a script - wp_register_script('mystat-map-loader', false); - wp_enqueue_script('mystat-map-loader'); - wp_add_inline_script('mystat-map-loader', - 'const mystat_track_points = ' . json_encode($track_points) . ';' . - 'document.addEventListener("DOMContentLoaded", function() { - if (typeof L === "undefined" || typeof mystat_track_points === "undefined" || mystat_track_points.length === 0) return; + wp_register_script('mystat-details-loader', false); + wp_enqueue_script('mystat-details-loader'); + + $map_script = ' + const track_points = ' . json_encode($gpx_data['points']) . '; + if (typeof L !== "undefined" && track_points.length > 0) { const map = L.map("mystat-activity-map"); L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: \'© OpenStreetMap contributors\' }).addTo(map); - const polyline = L.polyline(mystat_track_points, {color: "#' . esc_js( ltrim($activity->color, '#') ) . '"}).addTo(map); + const polyline = L.polyline(track_points, {color: "' . esc_js( $activity->color ) . '"}).addTo(map); map.fitBounds(polyline.getBounds().pad(0.1)); - L.marker(mystat_track_points[0]).addTo(map).bindPopup("Start"); - L.marker(mystat_track_points[mystat_track_points.length - 1]).addTo(map).bindPopup("Koniec"); - });' + L.marker(track_points[0]).addTo(map).bindPopup("Start"); + L.marker(track_points[track_points.length - 1]).addTo(map).bindPopup("Koniec"); + } + '; + + $elevation_chart_script = ''; + if ( ! empty( $gpx_data['elevation_profile'] ) ) { + $elevation_chart_script = ' + const elevation_data = ' . json_encode( $gpx_data['elevation_profile'] ) . '; + if (typeof Chart !== "undefined" && elevation_data.length > 0) { + const ctx = document.getElementById("mystat-elevation-chart").getContext("2d"); + new Chart(ctx, { + type: "line", + data: { + labels: elevation_data.map(p => p.distance), + datasets: [{ + label: "Wysokość (m)", + data: elevation_data.map(p => p.elevation), + borderColor: "' . esc_js( $activity->color ) . '", + backgroundColor: "' . esc_js( $activity->color ) . '20", + fill: true, + pointRadius: 0, + tension: 0.1 + }] + }, + options: { + responsive: true, maintainAspectRatio: false, + scales: { + x: { title: { display: true, text: "Dystans (km)" } }, + y: { title: { display: true, text: "Wysokość (m n.p.m.)" } } + }, + plugins: { legend: { display: false } } + } + }); + } + '; + } + + wp_add_inline_script('mystat-details-loader', + 'document.addEventListener("DOMContentLoaded", function() {' . $map_script . $elevation_chart_script . '});' ); } } @@ -1211,10 +1361,18 @@ function mystat_view_activity_page() { - +

Mapa Trasy

-
+
+ + +

Profil Wysokości

+
+ +
+ + gpx_url ) ) : ?>

Mapa Trasy

@@ -1790,7 +1948,7 @@ function mystat_single_activity_shortcode_handler( $atts ) { // Pobieranie danych z bazy $table_activities = $wpdb->prefix . 'mystat_activities'; $sql = $wpdb->prepare(" - SELECT a.*, c.name as category_name, et.name as event_type_name, eq.name as equipment_name + SELECT a.*, c.name as category_name, c.color as category_color, et.name as event_type_name, eq.name as equipment_name FROM $table_activities a LEFT JOIN {$wpdb->prefix}mystat_categories c ON a.category_id = c.id LEFT JOIN {$wpdb->prefix}mystat_event_types et ON a.event_type_id = et.id @@ -1817,37 +1975,75 @@ function mystat_single_activity_shortcode_handler( $atts ) { ob_start(); // Prepare map data if GPX exists - $track_points = []; + $gpx_data = []; if ( ! empty( $activity->gpx_url ) ) { - $track_points = mystat_get_track_from_gpx_url( $activity->gpx_url ); + $gpx_data = mystat_parse_gpx_data( $activity->gpx_url ); - if ( ! empty( $track_points ) ) { + if ( ! empty( $gpx_data['points'] ) ) { // Enqueue scripts for the frontend wp_enqueue_style('leaflet-css', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css'); wp_enqueue_script('leaflet-js', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', [], '1.9.4', true); + wp_enqueue_script('chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', [], null, true); $map_id = 'mystat-map-' . esc_attr( $activity->id ); + $chart_id = 'mystat-chart-' . esc_attr( $activity->id ); // Pass track data to a script - wp_register_script('mystat-shortcode-map-loader-' . $activity->id, false); - wp_enqueue_script('mystat-shortcode-map-loader-' . $activity->id); - wp_add_inline_script('mystat-shortcode-map-loader-' . $activity->id, + wp_register_script('mystat-shortcode-loader-' . $activity->id, false); + wp_enqueue_script('mystat-shortcode-loader-' . $activity->id); + + $map_script = ' + const trackPoints = ' . json_encode( $gpx_data['points'] ) . '; + const mapId = "' . esc_js($map_id) . '"; + + var container = L.DomUtil.get(mapId); + if(container != null) { container._leaflet_id = null; } + + const map = L.map(mapId); + L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { + attribution: \'© OpenStreetMap\' + }).addTo(map); + + const polyline = L.polyline(trackPoints, {color: "' . esc_js( $activity->category_color ) . '"}).addTo(map); + map.fitBounds(polyline.getBounds().pad(0.1)); + '; + + $elevation_chart_script = ''; + if ( ! empty( $gpx_data['elevation_profile'] ) ) { + $elevation_chart_script = ' + const elevationData = ' . json_encode( $gpx_data['elevation_profile'] ) . '; + const chartId = "' . esc_js($chart_id) . '"; + const ctx = document.getElementById(chartId).getContext("2d"); + new Chart(ctx, { + type: "line", + data: { + labels: elevationData.map(p => p.distance), + datasets: [{ + label: "Wysokość (m)", + data: elevationData.map(p => p.elevation), + borderColor: "' . esc_js( $activity->category_color ) . '", + backgroundColor: "' . esc_js( $activity->category_color ) . '20", + fill: true, pointRadius: 0, tension: 0.1 + }] + }, + options: { + responsive: true, maintainAspectRatio: false, + scales: { + x: { title: { display: true, text: "Dystans (km)" } }, + y: { title: { display: true, text: "Wysokość (m n.p.m.)" } } + }, + plugins: { legend: { display: false } } + } + }); + '; + } + + wp_add_inline_script('mystat-shortcode-loader-' . $activity->id, '(function() { document.addEventListener("DOMContentLoaded", function() { - if (typeof L === "undefined") return; - const trackPoints = ' . json_encode( $track_points ) . '; - const mapId = "' . esc_js($map_id) . '"; - - var container = L.DomUtil.get(mapId); - if(container != null) { container._leaflet_id = null; } - - const map = L.map(mapId); - L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { - attribution: \'© OpenStreetMap\' - }).addTo(map); - - const polyline = L.polyline(trackPoints, {color: "#3498db"}).addTo(map); - map.fitBounds(polyline.getBounds().pad(0.1)); + if (typeof L === "undefined" || typeof Chart === "undefined") return; + ' . $map_script . ' + ' . $elevation_chart_script . ' }); })();' ); @@ -1883,8 +2079,14 @@ function mystat_single_activity_shortcode_handler( $atts ) {
- -
+ +
+ + + +
+ +