Grupowanie danych JSON w tabeli - róża wiatrów

0

Witam!

Proszę o pomoc w następującym temacie. W bazie MySQL gromadzone są dane z czujników meteo m.in. prędkość i kierunek wiatru w stopniach i w postaci nazw geograficznych - dane te są aktualizowane co jedną minutę. Przykładowy rekord wygląda tak: **data_czas, wind_speed, wind_direction, wind_gust, compass. **

Z powyższych danych generuję plik JSON zawierający rekordy z ostatniej doby (1440):
http://www.dropbox.com/s/qrbeqj7v72ip31g/JSON.jpg?dl=0

Chciałbym z tych danych utworzyć tabelę (html) z wykorzystaniem Java Script, która mogłaby wyglądać tak:

  • wiersze tabeli reprezentują kierunek wiatru - 16 pozycji (N, W, S, E itd.)
  • kolumny tabeli reprezentują przedziały prędkości wiatru - 7 pozycji (<0.5, 0.5-2, 2-3 km/h itd.)
  • komórki tabeli mają zawierać udział procentowy wiatru z danego kierunku w określonym przedziale prędkości
    np. w ciągu ostatniej doby wiatr z kierunku południowego wiał z prędkością <0.5 km/h 10%, z prędkością 0.5-2 km/h 12% itd.

Należy więc posortować rekordy z ostatniej doby wg kierunków geograficznych, a następnie każdy kierunek na przedziały prędkości, prędkość z poszczególnych przedziałów przeliczyć na procenty i całość zaprezentować w tabeli. Tabela na stronie www musi być odświeżana co minutę bo będzie źródłem danych do tzw. róży wiatrów (wykres typu radar), a może jest całkiem inne podejście do stworzenia takiego wykresu z pliku JSON j.w.

Chciałbym z moich danych uzyskać wykres jak poniżej:
http://www.highcharts.com/demo/polar-wind-rose

Pozdrawiam serdecznie i życzę Szczęśliwego Nowego Roku

1
const data = [
    {czas_datetime: '2016-12-29 10:31:00', wind_speed_average: 4.55, compass: 'WSW'},
    {czas_datetime: '2016-12-29 10:32:00', wind_speed_average: 2.79, compass: 'SW'},
    {czas_datetime: '2016-12-29 10:33:00', wind_speed_average: 2.05, compass: 'ENE'},
    {czas_datetime: '2016-12-29 10:34:00', wind_speed_average: 1.82, compass: 'WSW'},
    {czas_datetime: '2016-12-29 10:35:00', wind_speed_average: 2.14, compass: 'SW'},
    {czas_datetime: '2016-12-29 10:36:00', wind_speed_average: 2.66, compass: 'SW'},
    {czas_datetime: '2016-12-29 10:37:00', wind_speed_average: 1.44, compass: 'ESE'},
    {czas_datetime: '2016-12-29 10:38:00', wind_speed_average: 4.72, compass: 'SW'},
    {czas_datetime: '2016-12-29 10:39:00', wind_speed_average: 3.22, compass: 'SW'},
    {czas_datetime: '2016-12-29 10:40:00', wind_speed_average: 4.49, compass: 'ENE'},
];

const getWindSpeedInterval = speed => {
    if (speed < 0.5) return '< 0.5';
    if (speed > 10) return '> 10';

    const intervals = [0.5, 2, 4, 6, 8, 10];
    const intervalStart = intervals.find((x, idx, array) => {
        return (speed - x) * (speed - array[idx + 1]) < 0
    });
    console.log(intervalStart);
    const intervalEnd = intervals[intervals.indexOf(intervalStart) + 1];

    return `${intervalStart} - ${intervalEnd}`;
};

const totals = data.reduce((totals, row) => {
    if(typeof totals[row.compass] === 'undefined') {
        totals[row.compass] = 0;
    }

    totals[row.compass] += row.wind_speed_average;

    return totals;
}, {});

const formattedData = data.reduce((data, { wind_speed_average, compass }) => {
    if(typeof data[compass] === 'undefined') {
        data[compass] = {};
    }

    const interval = getWindSpeedInterval(wind_speed_average);
    
    if(typeof data[compass][interval] === 'undefined') {
        data[compass][interval] = [];
    }
 
    data[compass][interval].push(wind_speed_average);
 
    return data;
}, {});

Czy jakoś tak sobie pokombinuj :P

0

Bardzo dziękuję!

Nie jestem pewien czy ogarnę ten ogrom wiedzy. W jaki sposób przekazuje się do tablicy data[] dane JSON generowane przez skrypt php? Dane w mojej bazie generuje skrypt data_compass.php?

P.S. Znalazłem w czeluściach internetu kod, który przybliżałby mnie do rozwiązania. Nie znam tylko formatu danych JSON pobieranych z adresu url (linia 39). Napisz proszę jak możnaby go przystosować do zmiennych w moim pliku JSON?

<https://gist.github.com/adamabernathy/eda63f14d79090ab1ea411a8df1e246e >

P.S. Teraz doczytałem - zaraz zamieszczę plik JSON (mój adres generujący go live). Sorki.

Pozdrawiam

1

JSFiddle

// helper przerabiający {} => []
const toArray = obj => Object.keys(obj).map(key => obj[key])

// funkcja przyjmująca wartość speed i zwracająca odpowiedni przedział
// dla podanej warości, dla przykładu:
// const intervals = [0.5, 2, 4, 6, 8, 10];
// getWindSpeedInterval(0.1, intervals) => '< 0.5'
// getWindSpeedInterval(12, intervals) => '> 10'
// getWindSpeedInterval(5.3, intervals) => '4 - 6'
const getWindSpeedInterval = (speed, intervals) => {
  const min = intervals[0];
  const max = intervals[intervals.length - 1];

  if (speed < min) return `< ${min}`;
  if (speed > max) return `> ${max}`;

  const intervalStart = intervals.find((x, idx, array) => {
    return (speed - x) * (speed - array[idx + 1]) <= 0
  });
  const intervalEnd = intervals[intervals.indexOf(intervalStart) + 1];
	
  return `${intervalStart} - ${intervalEnd}`;
};

const directions = [
  "N", "NNE", "NE", "ENE", "E", "ESE", "SE",
  "SSE", "S", "SSW", "SW", "WSW", "W", "WNW",
  "NW", "NNW"
];

const url = 'http://zbysiusp.dyndns.org/serwer_www/METEO3/data_compass_test.php?callback=?';

$.ajax({
  type: 'POST',
  dataType: 'json',
  url: '/echo/json/',
  data: {
  	json: getData()
  },
}).then(data => {
  // przerabiamy dane z formatu
  // [{czas_datetime...}, {czas_datetime...}] na format wymagany przez chart => 
  // {
  //   name: '2 - 4',
  //   1.5 odpowiada "N", 4.5 odpowiada "NNE", 3.0 odpowiada "NE" itd.
  //   data: [1.5, 4.5, 3.0, 0 ... ]
  // }
  const chartData = data.reduce((next, {
    wind_speed_average: speed, // zmieniamy nazwę na speed
    compass: direction // zmieniamy nazwę na direction
  }) => {
    // pobieramy przedział dla aktualnej prędkości wiatru
    const interval = getWindSpeedInterval(speed, [0.5, 2, 4, 6, 8, 10]);

    if (typeof next[interval] === 'undefined') {
      next[interval] = {
        name: interval,
         // tak nie tworzymy tablic, chyba że mamy do tego bardzo dobry powod
         // jak ja tutaj. Zazwyczaj zamiast new Array() robimy po prostu []
         // ja akurat chciałem stworzyć tablicę o ustalonej długości i wypełnioną 0
        data: new Array(directions.length).fill(0)
      };
    }
   
    // musimy zachować kolejność zgodną z wartościami w tablicy directions
    // bo te wartości będą robiły za labele w naszym charcie
    // dlatego też wyżej tworzyłem wypełnioną tablicę
    // a w tym miejscu wrzucamy wartość siły wiatru na 
    // pozycje pokrywającą się z pozycją kierunku z tablicy directions
    const directionIndex = directions.indexOf(direction);

    next[interval]['data'][directionIndex] += parseFloat(speed);

    return next;
  }, {});

  // powyższa operacja wyprodukowała nam {}, a my potrzebujemy żeby była to []
  const seriesData = toArray(chartData);

W swoim gotowym godzie zamień to:

$.ajax({
  type: 'POST',
  dataType: 'json',
  url: '/echo/json/',
  data: {
  	json: getData()
  },
})

na to

$.get(url)

Ta część kodu, jak i funkcja getData jest tylko na potrzeby dema w jsFiddle.

0

Bardzo dziękuję. Dużo materiału do analizy - gotowiec, dziękuję jeszcze raz. Tak na gorąco: na wygenerowanym wykresie typu radar w tooltipie pojawia się przedział prędkości i liczba trzycyfrowa - spodziewałem się udziału tego przedziału prędkości w procentach. Napisałeś również, że blok kodu AJAX jest na potrzeby dema, ale w gotowym kodzie mógłby służyć do odświeżania wykresu co określony czas np. co 1 minutę z wykorzystaniem setTimeout(funkcja, 60000). W dotychczasowych wykresach w Google Charts wykorzystywałem tą funkcję do wykonania skryptu data_compass.php co 1 minutę. Podobnie możnaby zrobić z adresem, a może się mylę?

 function drawChart_wiatr() {

        $.ajax({
          url: 'data_compass.php',
          dataType: 'json',
        }).done(function (results) {
			
		   setTimeout(drawChart_wiatr, 60000);

Podgląd mojej dotychczasowej pracy można zobaczyć na roboczej stronie:
http://zbysiusp.dyndns.org/serwer_www/METEO3/

Co do wykresu "wiatru" na tej stronie zastanawiam się, czy jest możliwość umieszczenia w tooltipie dodatkowej informacji oprócz stopni, jest np.: 180, a mogłoby być: 180, S (południe). Nie wiem czy Google API to umożliwia. Wykres powstaje z tych samych danych JSON co róża wiatrów.

1

To już musisz poczytać dokumentację tego pluginu. Ja Ci pomogłem z transformacją danych, bo to dla kogoś początkującego faktycznie może być tudne ;)

Jeżeli chodzi o odświeżanie, to pewnie, możesz to wrzucić w setTimeout i powinno działać tak samo. Nie cały blok kodu ajax jest na potrzeby dema. Jedynie jego forma. Jak spacja w spacje podmienisz dokładnie tak jak jest post wyżej, to nie zauważysz różnicy. Po prostu jsFiddle nie pozwala wykonywać żądań Ajax, więc musiałem to jakoś obejść.

0

Napisałem kod, ale mam problem z usunięciem funkcji getData(). Jak prawidłowo powinno wyglądać zapytanie AJAX dla skryptu PHP, a jak dla adresu? Chodzi o ten fragment kodu:

 const url = 'http://zbysiusp.dyndns.org/serwer_www/METEO3/data_compass_test.php?callback=?';
		
		$.ajax({
		  type: 'POST',
		  dataType: 'json',
		  url: 'data_compass.php',
		  data: {
			json: getData()
		  },
		}).then(data => {

Kod w całości wygląda tak, ale nie uruchamia się nawet z funkcją getData() i jej definicją. Coś pokręciłem. Dziękuję za cierpliwość.

 <!doctype html>
<html>
    <head>
    <meta charset="utf-8">
    
    <title>Dokument bez tytułu</title>
   
    <script src="https://code.highcharts.com/highcharts.js"></script>
    <script src="https://code.highcharts.com/highcharts-more.js"></script>
    <script src="https://code.highcharts.com/modules/data.js"></script>
    <script src="https://code.highcharts.com/modules/exporting.js"></script>
    <script src="https://code.jquery.com/jquery-3.0.0.js" integrity="sha256-jrPLZ+8vDxt2FnE1zvZXCkCcebI/C8Dt5xyaQBjxQIo=" crossorigin="anonymous"></script>
    
 	<script type="text/javascript">
	   
			// helper przerabiający {} => []
			const toArray = obj => Object.keys(obj).map(key => obj[key])
			
			// funkcja przyjmująca wartość speed i zwracająca odpowiedni przedział
			// dla podanej warości, dla przykładu:
			// const intervals = [0.5, 2, 4, 6, 8, 10];
			// getWindSpeedInterval(0.1, intervals) => '< 0.5'
			// getWindSpeedInterval(12, intervals) => '> 10'
			// getWindSpeedInterval(5.3, intervals) => '4 - 6'
			const getWindSpeedInterval = (speed, intervals) => {	
		  const min = intervals[0];
		  const max = intervals[intervals.length - 1];
		
		  if (speed < min) return `< ${min}`;
		  if (speed > max) return `> ${max}`;
		
		  const intervalStart = intervals.find((x, idx, array) => {
			return (speed - x) * (speed - array[idx + 1]) <= 0
		  });
		  const intervalEnd = intervals[intervals.indexOf(intervalStart) + 1];
		
		  return `${intervalStart} - ${intervalEnd}`;
		};
		
		const directions = [
		  "N", "NNE", "NE", "ENE", "E", "ESE", "SE",
		  "SSE", "S", "SSW", "SW", "WSW", "W", "WNW",
		  "NW", "NNW"
		];
		
		const url = 'http://zbysiusp.dyndns.org/serwer_www/METEO3/data_compass_test.php?callback=?';
		
		$.ajax({
		  type: 'POST',
		  dataType: 'json',
		  url: 'data_compass.php',
		  data: {
			json: getData()
		  },
		}).then(data => {
		  // przerabiamy dane z formatu
		  // [{czas_datetime...}, {czas_datetime...}] na format wymagany przez chart => 
		  // {
		  //   name: '2 - 4',
		  //   1.5 odpowiada "N", 4.5 odpowiada "NNE", 3.0 odpowiada "NE" itd.
		  //   data: [1.5, 4.5, 3.0, 0 ... ]
		  // }
		  const chartData = data.reduce((next, {
			wind_speed_average: speed, // zmieniamy nazwę na speed
			compass: direction // zmieniamy nazwę na direction
		  }) => {
			// pobieramy przedział dla aktualnej prędkości wiatru
			const interval = getWindSpeedInterval(speed, [0.5, 2, 4, 6, 8, 10]);
		
			if (typeof next[interval] === 'undefined') {
			  next[interval] = {
				name: interval,
				data: new Array(directions.length).fill(0)
			  };
			}
		
			const directionIndex = directions.indexOf(direction);
		
			next[interval]['data'][directionIndex] += parseFloat(speed);
		
			return next;
		  }, {});
		
		  // powyższa operacja wyprodukowała nam {}, a my potrzebujemy żeby była to []
		  const seriesData = toArray(chartData);
		
		  Highcharts.chart('container', {
			chart: {
			  polar: true,
			  type: 'column'
			},
			plotOptions: {
			  series: {
				stacking: 'normal',
				shadow: false,
				groupPadding: 0,
				pointPlacement: 'on'
			  }
			},
		
			title: {
			  text: 'Wind Rose'
			},
		
			xAxis: {
			  tickmarkPlacement: 'on',
			  categories: directions
			},
		
			yAxis: {
			  title: {
				text: 'Frequency'
			  },
			  reversedStacks: false
			},
		
			series: seriesData,
		  });
		});
		
   </script> 
    
    </head>

    <body>
    
        <div id="container" style="min-width: 420px; max-width: 600px; height: 400px; margin: 0 auto"></div>
    
    </body>
</html>
0

Dzięki za plik. Niestety mam problem z jego uruchomieniem - strona nie działa. W kodzie widzę podmianę wind_speed_average na speed, compass na direction, ale w pliku JSON przekazywany jest także kierunek w stopniach wind_direction_average, rozumiem że go pomijamy.

0

Jest na serwerze, ale nie działa. Edytor (Dreamweaver) pokazuje błąd składniowy w linii:

 const toArray = obj => Object.keys(obj).map(key => obj[key])

Może problem z nawiasami

1

Przetestowałem kod u siebie i działa bez problemu. Jak masz przeglądarke z zeszłego stulecia to spróbuj z tym:

<!doctype html>
<html>
<head>
    <meta charset="utf-8">

    <title>Dokument bez tytułu</title>

    <script src="https://code.highcharts.com/highcharts.js"></script>
    <script src="https://code.highcharts.com/highcharts-more.js"></script>
    <script src="https://code.highcharts.com/modules/data.js"></script>
    <script src="https://code.highcharts.com/modules/exporting.js"></script>
    <script src="https://code.jquery.com/jquery-3.0.0.js"
            integrity="sha256-jrPLZ+8vDxt2FnE1zvZXCkCcebI/C8Dt5xyaQBjxQIo=" crossorigin="anonymous"></script>
</head>
<body>
<div id="container" style="min-width: 420px; max-width: 600px; height: 400px; margin: 0 auto"></div>
<script type="text/javascript">
    "use strict";

    $(function () {
        // helper przerabiający {} => []
        var toArray = function toArray(obj) {
            return Object.keys(obj).map(function (key) {
                return obj[key];
            });
        };

        // funkcja przyjmująca wartość speed i zwracająca odpowiedni przedział
        // dla podanej warości, dla przykładu:
        // const intervals = [0.5, 2, 4, 6, 8, 10];
        // getWindSpeedInterval(0.1, intervals) => '< 0.5'
        // getWindSpeedInterval(12, intervals) => '> 10'
        // getWindSpeedInterval(5.3, intervals) => '4 - 6'
        var getWindSpeedInterval = function getWindSpeedInterval(speed, intervals) {
            var min = intervals[0];
            var max = intervals[intervals.length - 1];

            if (speed < min) return "< " + min;
            if (speed > max) return "> " + max;

            var intervalStart = intervals.find(function (x, idx, array) {
                return (speed - x) * (speed - array[idx + 1]) <= 0;
            });
            var intervalEnd = intervals[intervals.indexOf(intervalStart) + 1];

            return intervalStart + " - " + intervalEnd;
        };

        var directions = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"];

        $.get('data_compass_test.php?callback=').then(function (data) {
            data = JSON.parse(data);

            // przerabiamy dane z formatu
            // [{czas_datetime...}, {czas_datetime...}] na format wymagany przez chart =>
            // {
            //   name: '2 - 4',
            //   1.5 odpowiada "N", 4.5 odpowiada "NNE", 3.0 odpowiada "NE" itd.
            //   data: [1.5, 4.5, 3.0, 0 ... ]
            // }
            var chartData = data.reduce(function (next, _ref) {
                var speed = _ref.wind_speed_average,
                        direction = _ref.compass;

                // pobieramy przedział dla aktualnej prędkości wiatru
                var interval = getWindSpeedInterval(speed, [0.5, 2, 4, 6, 8, 10]);

                if (typeof next[interval] === 'undefined') {
                    next[interval] = {
                        name: interval,
                        data: new Array(directions.length).fill(0)
                    };
                }

                var directionIndex = directions.indexOf(direction);

                next[interval]['data'][directionIndex] += parseFloat(speed);

                return next;
            }, {});

            // powyższa operacja wyprodukowała nam {}, a my potrzebujemy żeby była to []
            var seriesData = toArray(chartData);

            Highcharts.chart('container', {
                chart: {
                    polar: true,
                    type: 'column'
                },
                plotOptions: {
                    series: {
                        stacking: 'normal',
                        shadow: false,
                        groupPadding: 0,
                        pointPlacement: 'on'
                    }
                },

                title: {
                    text: 'Wind Rose'
                },

                xAxis: {
                    tickmarkPlacement: 'on',
                    categories: directions
                },

                yAxis: {
                    title: {
                        text: 'Frequency'
                    },
                    reversedStacks: false
                },

                series: seriesData
            });
        });
    });
</script>
</body>
</html>

Jeżeli to nie działa to otwórz narzędzia deweloperskie (ctrl + shift + i) i powiedz czy masz błędy w konsoli.

0

"Nowa wersja" działa! Dziękuję bardzo.
Przeglądarkę Google Chrome mam w najnowszej wersji. Czy przedziały prędkości można dowolnie dostosować? Co oznacza trzycyfrowa wartość w tooltipie? Spodziewałem się wartości w procentach - udział danego przedziału, danego kierunku w całości.

http://zbysiusp.dyndns.org/serwer_www/METEO3/windrose2.php

0

Witam! Wciąż jestem na bardzo wczesnym etapie nauki korzystania wykresów Highcharts. W kodzie poniżej ujęta jest jedna seria danych. W jaki sposób dopisać kolejną (w danych JSON) tak aby wykres zawierał 2 kategorie: soki i dżemy (oś x), a każda kategoria po 3 kolumny (jabłka, pomarańcze, banany - wartość). Proszę o podpowiedź. Pozdrawiam.

 <!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>Update Series Data</title>
    <script type="text/javascript" src="js/jquery-1.9.1.js"></script>
    <script type="text/javascript" src="js/highcharts.js"></script>
    <script type="text/javascript">
        $(function () {
            var json = [
				{
					"key": "Jabłka",
					"value": "4",
				}, 
				{
					"key": "Banany",
					"value": "7",
				}, 
				{
					"key": "Pomarańcze",
					"value": "9",
				}
			];
			var processed_json = new Array();
			$.map(json, function(obj, i) {
				processed_json.push([obj.key, parseInt(obj.value)]);
			});


			$('#container').highcharts({
				chart: {
					type: 'column'
				},
				xAxis: {
					type: "category"
				},
				series: [{
					data: processed_json
				}]
			});
		});
    </script>
</head>
<body>
    <div id="container" style="height: 300px"></div>
</body>
</html>
0

Aktualnie wykres generuje się prawidłowo, gdy serie danych (przekształcone do formatu chart) umieszczę w opcjach wykresu, ale nie mogę ustalić jak powinien wyglądać wyjściowy plik JSON z tymi danymi (przed przekształceniem do formatu chart). Proszę o podpowiedź.

 $('#container').highcharts({
				chart: {
					type: 'column',
				},
				xAxis: {
					 
					type: "category"
					//categories:[]
				},
				
				series: [
							{
								//data: processed_json
								name: 'Soki', data:[70, 75, 65, 90, 85]},
								{name: 'Dżemy', data:[80, 45, 90, 85, 90]},
								{name: 'Desery', data:[80, 80, 90, 101, 90],
							}
						]
				
				
			});
		});
 $(function () {
           
		    var json = [
										
					???
						];
			
			var processed_json = new Array();
			$.map(json, function(obj, i) {
				processed_json.push([(obj.name), parseFloat(obj.data)]);
			});
0

Witam. Bardzo proszę aby szanowni Forumowicze pochylili się nad problemem z ostatniego postu. Od kilu dni nie mogę rozwiązać tego problemu. Kod nie działa poprawnie. Na osi x spodziewałem się lat 2010-2014, a przy każdym roku 3 serii danych (soki, dżemy, desery). Aktualnie na osi x jest rok 2010, 2011, 2012, a do każdego roku przypisane są po kolei soki, dżemy, desery. Coś jest nie tak z parsowaniem danych.

 <!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>Update Series Data</title>
    <script type="text/javascript" src="js/jquery-1.9.1.js"></script>
    <script type="text/javascript" src="js/highcharts.js"></script>
    <script type="text/javascript">
        $(function () {
           
		    var json = [{
				
			name: 'Soki', data:[70, 75, 65, 90, 85]},
			{name: 'Dżemy', data:[80, 45, 90, 85, 90]},
			{name: 'Desery', data:[80, 80, 90, 101, 90],
				
			}];
			
			var processed_json = new Array();
			$.map(json, function(obj, i) {
				processed_json.push([obj.name, parseFloat(obj.data)]);
			});


			$('#container').highcharts({
				chart: {
					type: 'column'
				},
				xAxis: {
					//type: "category"
					categories:['2010','2011','2012','2013','2014']
					
				},
				series: [
							{
								data: processed_json
							}
						]
				
				
			});
		});
    </script>
</head>
<body>
    <div id="container" style="height: 300px"></div>
</body>
</html>

Pozdrawiam

1 użytkowników online, w tym zalogowanych: 0, gości: 1