среда, 20 августа 2025 г.

ChartDirector, RazorPage, Chad, JS-timer

https://ask.chadgpt.ru/share/be4cddc1-2afc-43c5-9f71-b40d71d688b9

D:\VC2508\Charts\WpfChartSln\WpfChartDir\WpfChartDir.sln

D:\VC2508\Charts\WpfChartSln\WpfChartDir\RazorChartDirApp08\RazorChartDirApp08.csproj - work well

Работает с локальным Rendering

Разберу по частям, зачем здесь каждый фрагмент, как работает обновление, и почему редактор подчеркивает приведение типа (RazorChartViewer)ViewData["Viewer"] зеленой волной.

Что делает страница

  • Директивы @page, @model: это Razor Page. Модель страницы — RazorChartDirApp08.Pages.SimpleLineModel. В ней, как правило, в OnGet вы готовите данные и кладете в ViewData["Viewer"] экземпляр RazorChartViewer (обертка ChartDirector для Razor).
  • @using ChartDirector: подключает типы ChartDirector/RazorChartViewer.
  • ViewData["Title"] = "Simple Line Chart": задает заголовок.
  • var initialHtml = ((RazorChartViewer)ViewData["Viewer"]).RenderHTML();: берёт из ViewData подготовленный на сервере Viewer, приводит к RazorChartViewer и рендерит стартовый HTML (обычно это <img ... /> плюс <map ...> для тултипов/кликабельных регионов).
  • В разметке <div id="chart-container"> @Html.Raw(initialHtml) </div>: вставляет HTML диаграммы в контейнер.
  • Скрипт: каждые intervalMs миллисекунд делает GET на эндпоинт @Url.Page("SimpleLine", "ChartHtml") и получает свежий HTML диаграммы. Затем заменяет container.innerHTML целиком, чтобы одновременно обновить <img /> и связанный <map> (иначе сломаются usemap/тултипы).

Почему обновление без мерцания

  • Вы не перерисовываете весь документ, а только содержимое одного контейнера.
  • Меняете сразу и картинку, и карту областей. Это важно: если заменить только <img />, то подсказки и кликабельные зоны могут отстать.
  • fetch с cache: 'no-store' гарантирует, что браузер не возьмет кэш, а запросит новый HTML/изображение.

Про адрес @Url.Page("SimpleLine", "ChartHtml")

  • Это вызов хендлера OnGetChartHtml в SimpleLine.cshtml.cs. Он должен вернуть ContentResult с text/html, где вы делаете viewer.RenderHTML() заново на каждом запросе (и, возможно, обновляете данные перед этим).

Зачем ViewData["Viewer"]

  • В OnGet вы создаете данные и на их основе создаете ChartDirector.Chart и RazorChartViewer. Viewer вы кладете в ViewData["Viewer"], чтобы страница могла получить готовый HTML уже при первом рендере без ожидания fetch.

Почему редактор подчеркивает (RazorChartViewer)ViewData["Viewer"] зеленой волной

  • Зеленая волнистая — это предупреждение (warning), а не ошибка. Обычно это:
    • CS8619/CS8600/CS8602/CS8605 из-за nullable reference types: ViewData возвращает object? (может быть null), и явное приведение к RazorChartViewer без проверки на null рискует NRE. Редактор предупреждает о потенциальном null.
    • Или IDE0004/IDE0019/IDE0020 стилистика: явное приведение может считаться небезопасным/избыточным с точки зрения стиля, лучше использовать безопасное «as» и проверить на null, или вынести в локальную переменную с pattern matching.
  • В Razor ViewData — это словарь Dictionary<string, object?>. Компилятор не знает, что по ключу "Viewer" всегда лежит RazorChartViewer и не null, поэтому и ругается.

Как убрать предупреждение корректно

  • Вариант с безопасным приведением и проверкой:
    @{
    var viewer = ViewData["Viewer"] as RazorChartViewer;
    if (viewer == null)
    {
    throw new InvalidOperationException("Viewer is not provided in ViewData[&quot;Viewer&quot;].");
    }
    var initialHtml = viewer.RenderHTML();
    }
  • Вариант с pattern matching:
    @{
    if (ViewData["Viewer"] is RazorChartViewer viewer)
    {
    var initialHtml = viewer.RenderHTML();
    }
    else
    {
    throw new InvalidOperationException("Viewer is not provided or has wrong type.");
    }
    }
  • Если вы уверены, что null быть не может, можно использовать оператор null-forgiving, чтобы успокоить анализатор, но это на ваш риск:
    @{
    var initialHtml = ((RazorChartViewer)ViewData["Viewer"]!).RenderHTML();
    }
  • Или задать строго типизированную модель/свойство, чтобы не тащить через ViewData:
    В модели страницы:
    public RazorChartViewer Viewer { get; private set; } = default!;
    public void OnGet()
    {
    // ... создаете chart и viewer
    Viewer = yourViewer;
    }
    В Razor:
    var initialHtml = Model.Viewer.RenderHTML();
    Это самый чистый и безопасный способ, а предупреждение исчезнет, потому что тип известен.

Типичные места, где еще появляются такие предупреждения

  • Когда используете HttpContext.Items, TempData, ViewBag/ViewData — везде, где тип object?.
  • Когда в проекте включены nullable reference types (обычно в .csproj <nullable>enable</nullable>), анализатор строго следит за возможными null.

Итого

  • Код у вас правильный по логике и архитектуре: стартовый HTML — через сервер, дальше — легкое обновление через fetch без мерцания.
  • Зеленая волна — это предупреждение анализа null-безопасности из-за приведения типа из ViewData. Исправьте через as + проверка, pattern matching, null-forgiving оператор, либо, лучше, используйте свойство Model.Viewer и не храните viewer в ViewData.
А вот сообщения в консоли выводятся warn
warn: Microsoft.WebTools.BrowserLink.Net.BrowserLinkMiddleware[3]
Unable to configure Browser Link script injection on the response.
ЧТо делать ?
Это предупреждение связано с Browser Link в Visual Studio. Оно безвредно и появляется, когда middleware пытается внедрить скрипт в ответ, а тип ответа или пайплайн этому не способствует (например, JSON, streaming, уже начатый ответ, отключены cookies, HTTPS/HTTP2 с определенными настройками, или вы не используете стандартный Razor Layout).

Что можно сделать:

  • Проще всего: отключить Browser Link. В Visual Studio откройте меню Debug > Options > Projects and Solutions > Web Projects и снимите галочку Enable Browser Link. Либо на панели VS (обычно вверху) отключите кнопку Browser Link.
  • В appsettings.Development.json установите:
    {
    "Logging": {
    "LogLevel": {
    "Microsoft.WebTools.BrowserLink": "None"
    }
    }
    }
    Это просто скрывает предупреждение в логах.
  • Удалите/комментируйте UseBrowserLink в Startup/Program для Development:
    if (app.Environment.IsDevelopment())
    {
    // app.UseBrowserLink(); // отключить
    }
  • Если хотите оставить Browser Link, но уменьшить предупреждения, не отдавайте через тот же middleware ответы, куда нельзя внедрять скрипты. Например, для API-эндпоинтов, которые возвращают JSON/ChartDirector изображения/stream, держите их под /api и не пропускайте через Razor/Layout. Это не всегда избавит от предупреждения, но сократит частоту.
  • Очистите кэш Browser Link. В Visual Studio закройте все браузеры, остановите отладку, удалите папку %LocalAppData%\Microsoft\VisualStudio{версия}\Browser Link, перезапустите.
  • Если используете HTTPS с HTTP/2, иногда помогает принудительно использовать HTTP/1.1 для отладки, потому что Browser Link опирается на инъекцию скрипта и не всегда дружит с определенными комбинациями заголовков и протокола.

Итого: это не ошибка и не влияет на ваш функционал графиков. Самый быстрый вариант — отключить Browser Link в VS или убрать UseBrowserLink в Development.

--------------------------------------------------------------------------------------------

D:\VC2508\Charts\WpfChartSln\WpfChartDir\RazorChartDirApp07\RazorChartDirApp07.csproj

Под “JS-таймером” я имел в виду обычные функции таймера в браузере: setInterval и setTimeout. Они просто планируют вызов вашего кода через заданный интервал. Сейчас вы используете таймер, но дергаете window.location.reload(), из‑за этого перезагружается вся страница, мигает меню и иконка Refresh.

Если цель — автообновлять только график, оставив страницу и меню статичными, замените полную перезагрузку на частичную подгрузку HTML или данных и обновление DOM. Есть три рабочих варианта под Razor Pages + ChartDirector:

  1. Подгружать готовый HTML графика из отдельного endpoint
  • В SimpleLineModel рендерить только график (как сейчас), но вынести это в отдельную страницу/handler, например /SimpleLine?handler=Chart.
  • В основной странице оставить контейнер <div id="chartHost"></div>.
  • Каждую секунду подменять innerHTML этого контейнера свежим HTML через fetch.

Пример:

Razor (страница с меню и контейнером):

  • Вместо @Html.Raw(ViewBag.Viewer.RenderHTML()):
    <div id="chartHost"></div>

@section Scripts {
<script>
async function refreshChart() {
try {
const r = await fetch('@Url.Page("SimpleLine", null, new { handler = "Chart" }, Context.Request.Scheme)', { cache: "no-store" });
const html = await r.text();
document.getElementById('chartHost').innerHTML = html;
} catch(e) {
console.error(e);
}
}
refreshChart();
const timerId = setInterval(refreshChart, 1000);
// при навигации можно clearInterval(timerId)
</script>
}

C# (в той же Razor Page):
public IActionResult OnGetChart()
{
ViewData["Title"] = "Simple Line Chart";
var viewer = new RazorChartViewer(HttpContext, "chart1");
createChart(viewer);
// Вернем только HTML графика без layout
return Content(viewer.RenderHTML(), "text/html");
}

  1. Возвращать только данные (JSON) и перерисовывать график клиентом
  • Написать OnGetData(), который отдает массив значений синуса и метки.
  • На клиенте рисовать через Canvas/SVG библиотеку (Chart.js, ECharts, uPlot). Плюс: вообще не дергаете ChartDirector на каждом тике; минус: надо один раз настроить клиентскую библиотеку.
  1. Если ChartDirector отдает URL на изображение/SVG, обновлять только src
  • Если viewer.RenderHTML() вставляет <img src="..." />, то можно менять src на тот же URL с anti-cache параметром, не перерисовывая остальной DOM.
    Пример:
  • Оберните <img /> в контейнер с id, найдите <img /> и раз в секунду делайте img.src = baseUrl + "?t=" + Date.now();
  • Если это SVG-вставка, можно подменять весь innerHTML контейнера через fetch как в варианте 1.

Почему мигал браузер

  • window.location.reload() полностью перезагружает документ, поэтому перерисовывается весь layout, меню, обновляется вкладка — это норма для полной перезагрузки.
  • Таймер тут лишь триггер; “волшебство” в том, что вместо reload вы вызываете частичную загрузку и замену нужного куска DOM.

Минимальная правка вашего кода

  • Замените блок Scripts на:

@section Scripts {
<script>
async function refreshChart() {
try {
const res = await fetch('@Url.Page("SimpleLine", null, new { handler = "Chart" }, Context.Request.Scheme)', { cache: "no-store" });
const html = await res.text();
document.getElementById('chartHost').innerHTML = html;
} catch (err) {
console.error('Refresh failed', err);
}
}
refreshChart();
const timerId = setInterval(refreshChart, 1000);
</script>
}

  • И в разметке вместо текущего блока с RenderHTML выведите только контейнер:
    <div id="chartHost"></div>
  • Добавьте в PageModel handler OnGetChart(), который создает viewer и возвращает Content(viewer.RenderHTML(), "text/html"); как показано выше.

Так вы сохраните стабильный layout, а обновляться будет только график. Если расскажете, какой HTML генерирует RazorChartViewer (img или inline svg), подскажу самый простой способ адресной подмены. Если хотите совсем без опроса, можно перейти на SignalR/WebSocket и пушить обновления по событию.

RazorPage, Project, ChartDirector, Sine

RazorPage, Project, ChartDirector, Sine

D:\VC2508\Charts\WpfChartSln\WpfChartDir\WpfChartDir.sln

D:\VC2508\Charts\WpfChartSln\WpfChartDir\RazorChartDirApp07\RazorChartDirApp07.csproj

ChartDirector, Синусоида, work well

Page work well

---------------------------------------------------------------------------
SimpleLine.cshtml

@* @page

@model RazorChartDirApp07.Pages.SimpleLineModel

@{

} *@


@page

@model SimpleLineModel

@using ChartDirector

@{

    Layout = "_Layout";

}


@{

    if (ViewBag.Viewer is Array)

    {

        // Display multiple charts

        for (int i = 0; i < ViewBag.Viewer.Length; ++i)

        {

            @:@Html.Raw(ViewBag.Viewer[i].RenderHTML())

        }

    }

    else

    {

        // Display one chart only

        @:@Html.Raw(ViewBag.Viewer.RenderHTML())

    }

}


@section Scripts {

    <script>

        setTimeout(function () { window.location.reload(); }, 1000);

    </script>

}

-------------------------------
SimpleLine.cshtml.cs
------------------------------

using ChartDirector;

using LinSpace;

using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.RazorPages;

using RazorChartDirApp07.Services;


namespace RazorChartDirApp07.Pages

{

    public class SimpleLineModel : PageModel

    {

        private readonly IRunningSineState _state;

        private readonly ILogger<IndexModel> _logger;


        public SimpleLineModel(IRunningSineState state, ILogger<IndexModel> logger)

        {

            _state = state;

            _logger = logger;

        }

        public void OnGet()

        {

            ViewData["Title"] = "Simple Line Chart";

            RazorChartViewer viewer = new RazorChartViewer(HttpContext, "chart1");

            ViewData["Viewer"] = viewer;

            createChart(viewer);

        }

        private void createChart(RazorChartViewer viewer)

        {

            _logger.Log(LogLevel.Information, "CurrentIndex: " + _state.CurrentIndex.ToString());

            var dataArg = MathSeries.Linspace(0.0, 2 * Math.PI, 36, _state.CurrentIndex);

            var data = MathSeries.Evaluate(dataArg, Math.Sin);

            var nextIndex = _state.NextIndex(36);

            _logger.Log(LogLevel.Information, "CurrentIndex: " + _state.CurrentIndex.ToString());

            // The labels for the line chart

            string[] labels = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12",

                                "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24",

                                "25", "26", "27","28", "29", "30", "31","32","33", "34", "35"};


            // Create a XYChart object of size 250 x 250 pixels

            XYChart c = new XYChart(450, 450);


            // Set the plotarea at (30, 20) and of size 200 x 200 pixels

            c.setPlotArea(30, 20, 400, 400);


            // Add a line chart layer using the given data

            c.addLineLayer(data);


            // Set the labels on the x axis.

            c.xAxis().setLabels(labels);


            // Display 1 out of 3 labels on the x-axis.

            c.xAxis().setLabelStep(3);


            // Output the chart

            viewer.Image = c.makeWebImage(Chart.SVG);


            // Include tool tip for the chart

            viewer.ImageMap = c.getHTMLImageMap("", "",

                "title='Hour {xLabel}: Traffic {value} GBytes'");

        }

    }

}


вторник, 19 августа 2025 г.

WpfChartSln, Projects

WpfChartSln, Projects

D:\VC2508\Charts\WpfChartSln\WpfChartDir\WpfChartDir.sln

Simple Line Chart

BlazorServChartApp01.csproj - work 

BlazorServChartApp07.csproj - work

----------------------------------------------------

RazorChartDirApp01.csproj - Symbol Line - work

RazorChartDirApp02.csproj - does Not work

RazorChartDirApp03.csproj - does Not work

RazorChartDirApp04.csproj - work WITHOUT CHARTS

RazorChartDirApp05.csproj - work

Reaction Path

WpfChartDirApp01.csproj - work

воскресенье, 17 августа 2025 г.