Доброго времени суток! PS: Пишу сие от жары, что устал немного, ну... и в надежде что это кому-то поможет. Сам через такое прошел не так давно. Всё рассматриваемое реализация того что описано: https://forum.amperka.ru/threads/Разговоры-на-технические-темы.22016/page-12#post-314348 А именно построение графиков в окне браузера, отображение данных и прочее. ШАГ 1: Начинается всё с получения данных от сервера... WEB сервера конечно. Точнее Вашего локального WEB сервера на устройстве (предположительно МАЛИНЫ, но можно и другого). Предполагается что на этом устройстве крутится ещё что-то полезное, данные которого надо отображать в браузере. Сначала получение данных просто обращаясь к скрипту на этом сервере из браузера. А почему не напрямую, а через скрипт? Да потому что скрипт, в моём случае PHP, но можно Питон и прочее может выполнять что-то еще - к примеру обращаться к базе данных например MySQL. Обратившись к файлу MyPHP.php из распакованного архива: DataSVGtest.zip - это архив всей страницы WEB сервера (в моём случае Apache). Вы должны получить данные в формате JSON в окне браузера. Это содержимое файла "data/abpupowerauto.json" - это набор данных, которые надо отобразить: -- массив "ДАТА и ВРЕМЯ" в формате UTC -- массив "НАПРЯЖЕНИЕ" -- массив "ЧАСТОТА" эти параметры приведены как пример от реальных измерений. Следует отметить, что данные в реальности постоянно перезаписываются и накопитель в виде NAND в связи с недолговечностью непригоден. В нашем случае это просто статический пример пример. PS: Простите, но описано всё будет пошагово.
ШАГ2: Теперь надо, чтобы загружаемая страница отображала область SVG: И загружала данные самостоятельно с интервалом времени. Это можно проконтролировать с помощью браузера (у меня Firefox) МЕНЮ: Инструменты-->Инструменты браузера-->Инструменты вэб-разработчика. в Опции "Отладчик" в файле "index.js" в функции: Код (Javascript): //Запросы серверу var dbI; function LoadIndex() { var str; var xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = function () { if(xmlHttp.readyState == 4) { if(xmlHttp.status == 200) { str = xmlHttp.responseText; //document.getElementById("test").innerHTML = str; dbI = eval('(' + str +')'); ParseIndex(); } } } xmlHttp.open("GET", "MyPHP.php?dtime=" + new Date().getTime(), true); xmlHttp.send(); } Сделать точку останова в строке с содержимым ParseIndex(); и при остановке навести курсор на значение dbI в предыдущей строке. должны быть определены: Код (Text): F220: (800) [...] U220: (800) [...] dtime: (800) [...] Значит мы получаем с интервалом: Код (Javascript): const tloop = 1000; //таймаут ms цикла данных данные от нашего сервера: --частота 800 значений --напряжение 800 значений --дата и время 800 значений Учтите, что ваша программа (если не этот пример) на сервере должна перезаписывать/изменять массивы в файле "abpupowerauto.json". Это только пример. Архив страницы на сервере: DataSVGtest.zip
Итак данные принимаем, заготовлена ВЭБ страница. ШАГ3: Приступаем к созданию координатной сетки, если нужна: Следует отметить, что для работы были описаны ряд функций в отдельный файл - язык не поворачивается назвать библиотекой. Файл "graphSVG.js" содержит функции для работы: -- Инициализация "холста" -- Фиксация шаблона и работа с ним -- Рисование ЛИНИИ и ПРЯМОУГОЛЬНИКА на холсте -- Вывод текстовой строки на холст -- Получение координат курсора мыши на холсте Можно конечно всё это делать и своими функциями, но уж больно скучно. Следует иметь в виду что в данном примере реализовано два цикла: --Цикл данных (получение от сервера): Код (Javascript): //цикл данных function MyLoop() { LoadIndex(); setTimeout(MyLoop, tloop); } --Цикл графики: Код (Javascript): //цикл графики function LoopGraph() { ClearGraphSVG(); //обновляем холст из шаблона, а не рисуем каждый раз setTimeout(LoopGraph, tgraph); } Но стартовая функция не только запускает эти циклы, и инициализирует параметры холста: Код (Javascript): unction MyStart() { // InitGrarhSVG("MySVG", Xmax, Ymax); //инициализируем холст GrawGrid(); //рисуем сетку SetPatternSVG(); //принимаем весь холст с сеткой как шаблон //активация "циклов" MyLoop(); LoopGraph(); } Архив с примером: DataSVGtest.zip
Ну теперь всё готово для отображения данных. Приступим... ШАГ4: На текущем шаге определяем масштабы параметров ЧАСТОТА и НАПРЯЖЕНИЕ, нанесение значений на сетку, отображение параметров в виде графиков из массивов, отображение текущих значений в левом верхнем углу холста. Код (Javascript): //цикл графики function LoopGraph() { ClearGraphSVG(); //обновляем холст из шаблона, а не рисуем каждый раз // if(DataFlag == true) { var cnt = 0; var sx = 1; var x1 = 0; //график (из отрезков) на холст while(cnt < 799) { var x2 = x1 + sx; //в левом верхнем углу последнее измерение - реального времени if(cnt == 0) { DrawText("rU220t", 5, 15, '12px', "rgb(255,0,0)", U220[cnt] + "V"); } DrawLine("U220" + cnt, x1, 201 - ((U220[cnt] - 230) * GU220s), x2, 201 - ((U220[cnt + 1] - 230) * GU220s), "1px", "rgb(255,0,0)" ); x1 = x2; cnt++; } var x1 = 0; cnt = 0; //график (из отрезков) на холст while(cnt < 799) { var x2 = x1 + sx; //в левом верхнем углу последнее измерение - реального времени if(cnt == 0) { DrawText("rF220t", 5, 27, '12px', "rgb(0,255,0)", F220[cnt] + "Hz"); } DrawLine("F220" + cnt, x1, 201 - ((F220[cnt] - 50) * GF220s), x2, 201 - ((F220[cnt + 1] - 50) * GF220s), "1px", "rgb(0,255,0)" ); x1 = x2; cnt++; } //параметры (масштаб) на сетке cnt = 1; while(cnt < 4) { var uf = Math.round((startscale + (cnt * scale)) * Math.pow(10,2)) / Math.pow(10,2); DrawText("U220t" + cnt, 105, 401 - (cnt * 100) - 5, '12px', "rgb(255,0,0)", uf + "V"); var df = Math.round((fstartscale + (cnt * fscale)) * Math.pow(10,2)) / Math.pow(10,2); DrawText("F220t" + cnt, 705, 401 - (cnt * 100) - 5, '12px', "rgb(0,255,0)", df + "Hz"); cnt++; } var uf = Math.round((startscale + (cnt * scale)) * Math.pow(10,2)) / Math.pow(10,2); DrawText("U220t" + cnt, 105, 401 - (cnt * 100) + 15, '12px', "rgb(255,0,0)", uf + "V"); var df = Math.round((fstartscale + (cnt * fscale)) * Math.pow(10,2)) / Math.pow(10,2); DrawText("F220t" + cnt, 705, 401 - (cnt * 100) + 15, '12px', "rgb(0,255,0)", df + "Hz"); //ДАТА и ВРЕМЯ на сетке cnt = 0; while(cnt < 8) { var Dstr = timeConverter(dtime[cnt * 100] * 1000, 255); var Tstr = timeConverter(dtime[cnt * 100] * 1000, 0); DrawText("Date" + cnt, (cnt * 100) + 5, 401 - 5, '12px', "white", Dstr); DrawText("Time" + cnt, (cnt * 100) + 5, 401 - 17, '12px', "white", Tstr); cnt++; } } setTimeout(LoopGraph, tgraph); } А парсинг в цикле данных - определение масштаба шкалы и т.п. Код (Javascript): function ParseIndex() { var cnt = 0; U220max = 0; U220min = 999; F220max = 0; F220min = 999; //определяем максимумы и минимумы параметров и заполнение массивов while(cnt < 800) { U220[cnt] = parseFloat(dbI.U220[cnt]); if(U220max < U220[cnt]) U220max = U220[cnt]; if(U220min > U220[cnt]) U220min = U220[cnt]; F220[cnt] = parseFloat(dbI.F220[cnt]); if(F220max < F220[cnt]) F220max = F220[cnt]; if(F220min > F220[cnt]) F220min = F220[cnt]; dtime[cnt] = parseInt(dbI.dtime[cnt], 10); cnt++; } //напряжение - масштаб шкалы if((Math.abs(U220max - 230) < 4) && (Math.abs(U220min - 230) < 4)) { GU220s = 50; //200px / 4v scale = 2; startscale = 226; } else if((Math.abs(U220max - 230) < 10) && (Math.abs(U220min - 230) < 10)) { GU220s = 20; //200px / 10v scale = 5; startscale = 220; } else if((Math.abs(U220max - 230) < 20) && (Math.abs(U220min - 230) < 20)) { GU220s = 10; //200px / 20v scale = 10; startscale = 210; } else if((Math.abs(U220max - 230) < 40) && (Math.abs(U220min - 230) < 40)) { GU220s = 5; //200px / 40v scale = 20; startscale = 190; } else if((Math.abs(U220max - 230) < 100) && (Math.abs(U220min - 230) < 100)) { GU220s = 2; //200px / 100v scale = 50; startscale = 130; } else if((Math.abs(U220max - 230) < 200) && (Math.abs(U220min - 230) < 200)) { GU220s = 1; //200px / 200v scale = 100; startscale = 30; } /*else if((Math.abs(U220max - 230) < 230) && (Math.abs(U220min - 230) < 230)) { GU220s = 1; scale = 115; startscale = 0; }*/ //частота - масштаб шкалы if((Math.abs(F220max - 50) < 0.01) && (Math.abs(F220min - 50) < 0.01)) { GF220s = 10000; fscale = 0.01; fstartscale = 49.98; } else if((Math.abs(F220max - 50) < 0.1) && (Math.abs(F220min - 50) < 0.1)) { GF220s = 1000; fscale = 0.1; fstartscale = 49.8; } else if ((Math.abs(F220max - 50) < 2) && (Math.abs(F220min - 50) < 2)) { GF220s = 100; // 200px / 2Hz fscale = 1; fstartscale = 48; //49.8; } else if ((Math.abs(F220max - 50) < 5) && (Math.abs(F220min - 50) < 5)) { GF220s = 40; //200px / 5Hz fscale = 2.5; fstartscale = 45; } else { //if ((Math.abs(F220max - 50) < 10) && (Math.abs(F220min - 50) < 10)) { GF220s = 20; //200px / 10Hz fscale = 10; fstartscale = 30; } // DataFlag = true; } Архив: DataSVGtest.zip PS: А вот определение параметров по маркеру, по координатам курсора на холсте, надо или нет - не знаю.
ШАГ5: Скриншот сделать не мог, а фото с экрана что-то с чем-то. Когда наводится курсор мыши на полотно появляется на полотне окошко с данными и маркер (вертикальная линия) так же как тут: https://forum.amperka.ru/threads/Разговоры-на-технические-темы.22016/page-12#post-314348 Собственно архив: DataSVGtest.zip Ну вроде всё. PS: Основной смысл в неиспользовании программ на стороне пользователя - только браузер. Если кто испытал, будьте добры сообщить что за браузер и каков результат. У меня Firefox с ОС Didian. Я это к тому, что у меня получалось на разных машинах иметь разные результаты отображений. Правда к SVG это не относилось, но всё же! Спасибо!
Вот с танцами с бубном сделал скриншот: Дело в том, что уходом курсора из области холста макер и окно с данными исчезают. Курсор опять не отобразился на скриншоте. PS: Ребята скажите а у Вас сработало. Мне интересно про разные браузеры. У меня Firefox 115.12.0esr (64-разрядный) и ОС 5.10.0-30-amd64 #1 SMP Debian 5.10.218-1 (2024-06-01) x86_64 GNU/Linux Спасибо!
...итак: Никто не сказал как страница отображается у Вас в браузере Может буквы уплыли и ещё что-то. Ну да ладно. Допустим всё отображается нормально. А почему в JSON? Да потому, что все браузеры их понимают их и JS браузера спокойно их парсит: Код (Javascript): dbI = eval('(' + str +')'); //парсинг строки "str" в "dbI" Формат JSON можно посмотреть: https://habr.com/ru/articles/554274/ Данные в примере, что непрерывно перезаписываются в файл "abpupowerauto.json", получаем от устройства https://owen.ru/uploads/re_mu110-6u_1833.pdf По Modbus RTU. Эти данные записываются в буферы (массивы) таким образом, что в начало массивов записываются новые денные, когда перед записью все массивы "сдвигаются" вправо. В цикле Массивы перезаписываются в файл. Для этих операций я применил программу на Си, которая запускается на устройстве с ВЭБ сервером. Функция записи файла JSON: Код (C++): //вывод в файл JSON массива мониторинга питания автоматики int ffmjson(void) { int cnt = sz; // while(cnt > 0) { Um[cnt] = Um[cnt-1]; Fm[cnt] = Fm[cnt-1]; dtime[cnt] = dtime[cnt-1]; cnt--; } // FILE * file; file = fopen((const char*)(&namefile[0]),"wt"); if(file < 0) return -1; // fprintf(file, "{\n"); //начало JSON // ПАРАМЕТРЫ СТАТУСА СОЕДИНЕНИЙ - Это для MODMUS RTU fprintf(file, "\"constat_F1_RdInIO\" : \"%02X\",\n", f1data.constat_F1_RdInIO); fprintf(file, "\"constat_F1_RdOutIO\" : \"%02X\",\n", f1data.constat_F1_RdOutIO); fprintf(file, "\"constat_F1_WrOutIO\" : \"%02X\",\n", f1data.constat_F1_WrOutIO); fprintf(file, "\"constat_Upower\" : \"%02X\",\n", f1data.constat_Upower); // ДАТА и ВРЕМЯ в UTC - Массив в JSON cnt = 0; fprintf(file, "\"dtime\" : ["); while(cnt < (sz - 1)) { fprintf(file, "\"%lli\",", dtime[cnt]); cnt++; } fprintf(file, "\"%lli\"],\n", dtime[cnt]); // НАПРЯЖЕНИЕ - Массив в JSON cnt = 0; fprintf(file, "\"U220\" : ["); while(cnt < (sz - 1)) { fprintf(file, "\"%0.2f\",", Um[cnt]); cnt++; } fprintf(file, "\"%0.2f\"],\n", Um[cnt]); // ЧАСТОТА - Массив в JSON cnt = 0; fprintf(file, "\"F220\" : ["); while(cnt < (sz - 1)) { fprintf(file, "\"%0.2f\",", Fm[cnt]); cnt++; } fprintf(file, "\"%0.2f\"]\n", Fm[cnt]); fprintf(file, "}\n"); //окончание JSON // fclose(file); return 0; } Далее этот файл читается простым PHP скриптом при обращении ВЭБ страницы к ВЭБ серверу: PHP: <?php $cmd = "cat data/abpupowerauto.json"; $data = shell_exec($cmd); echo $data; ?>
Доброго времени суток! Хотел бы извиниться за мою невнимательность в вызове (точнее построение функции JS): Код (Javascript): //цикл графики function LoopGraph() { //ClearGraphSVG(); //обновляем холст из шаблона, а не рисуем каждый раз // if(DataFlag == true) { var cnt = 0; var sx = 1; var x1 = 0; ClearGraphSVG(); //обновляем холст из шаблона, а не рисуем каждый раз //график (из отрезков) на холст while(cnt < 799) { var x2 = x1 + sx; //в левом верхнем углу последнее измерение - реального времени if(cnt == 0) { DrawText("rU220t", 5, 15, '12px', "rgb(255,0,0)", U220[cnt] + "V"); } DrawLine("U220" + cnt, x1, 201 - ((U220[cnt] - 230) * GU220s), x2, 201 - ((U220[cnt + 1] - 230) * GU220s), "1px", "rgb(255,0,0)" ); x1 = x2; cnt++; } var x1 = 0; cnt = 0; //график (из отрезков) на холст while(cnt < 799) { var x2 = x1 + sx; //в левом верхнем углу последнее измерение - реального времени if(cnt == 0) { DrawText("rF220t", 5, 27, '12px', "rgb(0,255,0)", F220[cnt] + "Hz"); } DrawLine("F220" + cnt, x1, 201 - ((F220[cnt] - 50) * GF220s), x2, 201 - ((F220[cnt + 1] - 50) * GF220s), "1px", "rgb(0,255,0)" ); x1 = x2; cnt++; } //параметры (масштаб) на сетке cnt = 1; while(cnt < 4) { var uf = Math.round((startscale + (cnt * scale)) * Math.pow(10,2)) / Math.pow(10,2); DrawText("U220t" + cnt, 105, 401 - (cnt * 100) - 5, '12px', "rgb(255,0,0)", uf + "V"); var df = Math.round((fstartscale + (cnt * fscale)) * Math.pow(10,2)) / Math.pow(10,2); DrawText("F220t" + cnt, 705, 401 - (cnt * 100) - 5, '12px', "rgb(0,255,0)", df + "Hz"); cnt++; } var uf = Math.round((startscale + (cnt * scale)) * Math.pow(10,2)) / Math.pow(10,2); DrawText("U220t" + cnt, 105, 401 - (cnt * 100) + 15, '12px', "rgb(255,0,0)", uf + "V"); var df = Math.round((fstartscale + (cnt * fscale)) * Math.pow(10,2)) / Math.pow(10,2); DrawText("F220t" + cnt, 705, 401 - (cnt * 100) + 15, '12px', "rgb(0,255,0)", df + "Hz"); //ДАТА и ВРЕМЯ на сетке cnt = 0; while(cnt < 8) { var Dstr = timeConverter(dtime[cnt * 100] * 1000, 255); var Tstr = timeConverter(dtime[cnt * 100] * 1000, 0); DrawText("Date" + cnt, (cnt * 100) + 5, 401 - 5, '12px', "white", Dstr); DrawText("Time" + cnt, (cnt * 100) + 5, 401 - 17, '12px', "white", Tstr); cnt++; } //маркер /*var mp = GetSVGPoint(); //волучаем параметры курсора на холсте if(mp.MouseInBorders) { var Xmark = mp.x; var Ymark = mp.y; var Xmark0; var Ymark0; var width0 = 75; var height0 = 50; var Xmark1; var Ymark1; DrawLine("marker", Xmark, 0, Xmark, Ymax, "1px", "rgb(255,255,255)"); //отображаем маркер //координаты для отображения if(Xmark < (Xmax / 2)) { Xmark0 = Xmark + 25; Xmark1 = Xmark0; } else { Xmark0 = Xmark - 25; Xmark1 = Xmark0 - width0; } if(Ymark < (Ymax / 2)) { Ymark0 = Ymark + 25; Ymark1 = Ymark0; } else { Ymark0 = Ymark - 25; Ymark1 = Ymark0 - height0; } //окно для отображения DrawLine("marker1", Xmark, Ymark, Xmark0, Ymark0, "1px", "rgb(255,255,255)"); DrawRectangle("infbox", Xmark1, Ymark1, width0, height0, "rgb(255,255,255)", "rgb(10,10,10)", "0.7"); //отображение параметров в окне var Dstr = timeConverter(dtime[Xmark] * 1000, 255); var Tstr = timeConverter(dtime[Xmark] * 1000, 0); DrawText("DateMark", Xmark1 + 5, Ymark1 + 10, '12px', "white", Dstr); DrawText("TimeMark", Xmark1 + 5, Ymark1 + 10 + 12, '12px', "white", Tstr); DrawText("Umark", Xmark1 + 5, Ymark1 + 10 + 24, '12px', "rgb(255,0,0)", U220[Xmark] + "V"); DrawText("Fmark", Xmark1 + 5, Ymark1 + 10 + 36, '12px', "rgb(0,255,0)", F220[Xmark] + "Hz"); } */ DataFlag = false; } setTimeout(LoopGraph, tgraph); } Ошибка в том, что она перегружает браузер. Слишком часто перерисовывает, то что не требует изменений. PS: Я закоментировал узкие места. В другом рабочем проекте такого конечно такого нет. Но это просто пример, а не готовое решение. И Да! Тут ошибка, но именно в перегрузке браузера и ни в чём более.