Wykres użycia (CPU/Memory, etc).

0

Próbuję stworzyć wykres użycia CPU/Pamięci/etc w czasie rzeczywistym.
Dokładnie coś takiego, co widzimy w Menedżerze Zadań na zakładce wydajność w Windows. O, coś takiego: Wykres

Chodzi o wyrysowanie tego na TPaintBox, na canvasie. Bez żadnych komponentów i innych cudów... Po prostu wykres, który uaktualniany jest co zadaną jednostkę czasu (np. 1s) i który "przesuwany" jest z prawej do lewej strony... z pewnością wiecie o co chodzi...

Moje próby doprowadzają mnie do depresji... temat raczej jest trywialny, ale ja się nigdy nie bawiłem na poważnie canvasem... no i teraz mam :)
Bardzo proszę o jakiś konstruktywny przykład jak się do tego zabrać.

Próbowałem użyć Canvas.Polygon(). Ale to droga donikąd... zawsze wychodzi źle (chyba dlatego, że Polygon musi tworzyć zamkniętą figurę...
Czy używa się do tego kombinacji CanvasMoveTo() + Canvas.LineTo()? Czy jest inny sposób?

Bardzo proszę o jakieś podpowiedzi a najlepiej przykłądowy kod, jeśli ktoś z was już coś takiego robił.
-Pawel

1

Nie korzystałem w poligonu ale przykład z helpa powinien Tobie pomóc. I tak, poligon jest zamkniętą figurą. Czyli musisz dodać dodatkowe punktu na osi X, które będą podstawą i zamkną figurę.
http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/Graphics_TCanvas_Polygon.html

0

Ten przykład z pomocy pokazuje tylko jak użyć Canvas.Polygon... No przecież wiadomo jak. Chodzi mi bardziej, jak narysować taki wykres, który "żyje".
Polygon wydaje się być rozwiązaniem, ale nim nie jest. Bo "zamyka" wykres (z boku i dołu).

0
Pepe napisał(a):

Ten przykład z pomocy pokazuje tylko jak użyć Canvas.Polygon... No przecież wiadomo jak.

To widać po Twoim podsumowaniu z pierwszego posta:

Pepe napisał(a):

Próbowałem użyć Canvas.Polygon(). Ale to droga donikąd... zawsze wychodzi źle (chyba dlatego, że Polygon musi tworzyć zamkniętą figurę...

;)


Chodzi mi bardziej, jak narysować taki wykres, który "żyje".

Normalnie – masz bufor wartości Y o stałym rozmiarze (indeks określa X), na początku zerujesz wszystkie elementy, a po każdym ”ticku” przesuwasz go o jedno miejsce (tracąc najstarszą wartość), wpisujesz nową wartość na koniec i odmalowujesz wykres.

Polygon wydaje się być rozwiązaniem, ale nim nie jest.

Naszkicuj w Paint prowizorkę, a coś się doradzi.

Bo "zamyka" wykres (z boku i dołu).

No bo ma zamykać – zobacz jak wygląda ten systemowy wykres.

0

Raczej bez ponownego wrysowania całego Canvasa nie przejdzie. Ja bym zrobił listę z trzymanymi wartościami zużycia. Ustalasz sobie max rozmiar listy czyli ile tych punktów będziesz wyświetlał i aktualną ilość punktów żeby zacząć rysować od prawej strony (x_max - x_punktow). W Timerze dodajesz kolejne punkty do listy. Jeśli ilość punktów przekracza max to usuwasz pierwszy punkt (pozycje z listy). No i całość dalej działa na poligonie. Jeśli chciałbyś przesuwać aktualny obraz to:

  1. Będziesz musiał pokombinować żeby ukryć linię styku kolejno rysowanych pomiarów, o ile obramowanie będzie innego koloru niż wypełnienie.
  2. Cały obraz będzie trzymany w pamięci a wyświetlana tylko cześć. Przy dłuższym użyciu widziałbym nieadekwatne użycie pamięci przez program do jego funkcji.

Tak mi się wydaje.

edit. @furious programming mnie uprzedził, widać nie można robić sobie przerw podczas na usypianie dzieci podczas pisania posta :) ps. jak wywołać użytkownika z dwuczłonową nazwą?

0

O, ty prawisz mądrze. Zaraz wkleje mój testowy kod, ponieważ ciężko mi wytłumaczyć co mam a czego nie mam i czego oczekuje.
Mam nadzieje, że starzy forumowicze poprawią moje wypociny :)
-Pawel

0

Można by się tu buforem cyklicznym wspomóc – wtedy nie trzeba będzie implementować przesuwania całego bloku danych, bo nowa wartość automatycznie nadpisze najstarszą.


Jeśli chodzi o metodę Polygon – samo słowo polygon oznacza wielokąt, a wielokąt to zamknięta figura, więc metoda ta połączy ze sobą skrajne punkty. Dlatego też albo trzeba do całości dodać kilka punktów, w zależności od przypadku, albo skorzystać z metody Polyline – raz do namalowania samego wykresu i drugi raz (lub więcej) do dopełnienia całości.

Tyle że Polygon automatycznie obsługuje wypełnienie, więc można upiec dwie pieczenie na jednej metodzie. ;)

1

OK, w załączniku zasyłam kod...
Działa to prawie jak chce. ale prawie robi różnice. Rysunek: Wykres

Jak widać, z lewej strony, wykres powinien schodzić do dołu jak pokazałem niebieską cienką linią...
Z prawej strony, wykres powinien wchodzić z jakiejś wartości (po przejściu całej pętli), a nie od 0 (od zera tylko po rozpoczęciu odliczania).

Sprawdźcie mój kod... na pewno coś popieprzyłem :)
Również, systemowy wykres ładnie robi siatkę na wykresie... ja jak przesune procedure rysującą siatkę za procedure rysującą wykres uzyskuje "pocięty" wykres... tak nie może być :)
W ostateczności może być jak mam teraz, to jest siatka na stałe w tle, a wykres rysowany na siatce (a nie wraz z nią, co jest chyba robione w systemowym wykresie Microsoft).

KOD ŹRÓDŁOWY: http://www.meggamusic.co.uk/shup/1519512955/TEST.zip

0
Pepe napisał(a):

Jak widać, z lewej strony, wykres powinien schodzić do dołu jak pokazałem niebieską cienką linią...

Musisz użyć dwóch dodatkowych punktów – pierwszy musi posiadać współrzędną x taką samą jak pierwszy punkt w serii i y równe 0, natomiast drugi musi posiadac x taką samą jak ostatni punkt serii i też y równe 0. To 0 oznacza spód komponentu, czyli PaintBox.Height - 1. Będzie to inaczej wyglądać niż wykres systemowy, ale skoro tak chcesz to da się to zrobić.

Z prawej strony, wykres powinien wchodzić z jakiejś wartości (po przejściu całej pętli), a nie od 0 (od zera tylko po rozpoczęciu odliczania).

Będzie, o ile ostatni punkt serii będzie posiadał współrzędną x równą PaintBox.Width - 1.

Również, systemowy wykres ładnie robi siatkę na wykresie... ja jak przesune procedure rysującą siatkę za procedure rysującą wykres uzyskuje "pocięty" wykres... tak nie może być :)

Zwróć uwagę na to, że system używa antialiasingu i przezroczystości do malowania linii i wielokątów, czego nie wspiera klasa TCanvas. Jeśli potrzebujesz ładnego wykresu z gładkimi krawędziami to musisz skorzystać z jakiejś zewnętrznej biblioteki graficznej, np. Graphics32.

W ostateczności może być jak mam teraz, to jest siatka na stałe w tle, a wykres rysowany na siatce (a nie wraz z nią, co jest chyba robione w systemowym wykresie Microsoft).

Półprzezroczystą siatkę da się mimo wszystko namalować, tyle że w sposób nieco trudniejszy. Za pomocą ScanLine dobrać się do pamięci i każdy piksel, na którym ma być linia siatki, mieszać z kolorem siatki, używając ustalonego procentażu krycia (czyli odpowiednika kanału alpha).

Tyle że aby tego dokonać, wykres należy namalować na pomocniczej bitmapie (zwykły back buffering), a po skończeniu dłubania z kolorami, po prostu namalować bitmapę w komponencie.

0

Kombinowałem już z tymi 2 punktami. Za cholerę mi to nie wychodzi. Podejmie się ktoś wyprostowania tego kodu?

6

No, sklonowałem ten systemowy wykres – wyszło niemalże identycznie. :]

chart.png

Projekt stworzyłem w Lazarusie, bo tylko tego środowiska używam. W każdym razie da się zastosować kod w Delphi – trzeba tylko nanieść małe poprawki, ale to nie powinno być problemem. Najwyżej napisz z czym masz problem, a pomogę.

Poszedłem nieco na skróty i całość uprościłem, aby nie tracić czasu na skalowanie i związane z nim dodatkowe obliczenia. Wszystkie wymiary są podzielne przez 10 – rozmiar kontrolki to 600x300, odstępy siatki to 30 w pionie i 40 w poziomie, odstępy wierzchołków w poziomie to 10 (mowa o pikselach).

Aby wykres wyglądał tak samo jak ten systemowy, należy go namalować w pięciu krokach:

  • wypełnić tło komponentu – FillRect – bez obramowania, bo ono zostanie namalowane na samym końcu, aby przykryć obramowanie wielokąta wykresu (przyda się wtedy, gdy kolor obramowania komponentu będzie inny niż kolor obramowania wielokąta),
  • namalować wielokąt wykresu – Polygon – bez obramowania, bo siatka wizualnie znajduje się na tle wielokąta, ale pod jego obramowaniem, dlatego obramowania nie malujemy,
  • namalować siatkę linii – Line lub pary MoveTo i LineTo – przykryją tło komponentu i tło wielokąta,
  • namalować obramowanie wielokąta – Polyline – przykryje tło wielokąta oraz linie siatki,
  • namalować obramowanie komponentu – FrameRect – przykryje wszystko co do tej pory namalowaliśmy, czyli tło komponentu, tło wielokąta i jego obramowanie, a także linie siatki.

Wykres wygląda i zachowuje się dokładnie tak samo jak ten systemowy (no, oprócz przesuwającej się siatki i antialiasingu), więc jeśli potrzebujesz nieco inaczej go malować to będą potrzebne zmiany. IMO wszystko z nim w porządku.

Do załączników dodaję pełny projekt dla Lazarusa, razem z plikiem wykonywalnym do potestowania.

0

Hej, no bardzo ładnie! Dziękuję, że siedziałeś nad tym w nocy!
A przede wszystkim, to działa :) No, muszę dodać skalowanie.
Muszę przepisać to na Delphi analizując kod bo jestem bardzo ciekaw gdzie był problem u mnie (prawa strona wykresu).

Będę pisał, jak Lazarus stanie dęba :P
Ps: Dlaczego używasz Lazarusa a nie np. Delphi 10.2 Starter?

Edit:
No i przepisałem na Delphi... Nie działa :(
Proszę o spojrzenie co jest nie tak...
Zastąpiłem:

  • metodę Canvas.Line na Canvas.MoveTo + Canvas.LineTo,
  • wyrażenie x += y zastąpiłem x:= x+y;
  • zmienną LLeft zrobiłem globalną, bo w Delphi nie da się lokalnie inicjować zmienne przy deklaracji

Rezultat? Nie rysuje wykresu...
-Pawel

0
Pepe napisał(a):

Ps: Dlaczego używasz Lazarusa a nie np. Delphi 10.2.1 Starter?

Bo nie lubię pracować z łańcuchem u szyi i kulą u nogi.

Przesiadłem się na Lazarusa (dawno temu pisałem w Delphi7), bo jest darmowy, bo działa wydajnie nawet na starszej maszynie (nie potrzebuje RAM-u zbyt wiele). A najważniejsze jest to, że mam dostęp do pełnych źródeł biblioteki standardowej i biblioteki komponentów, dzięki czemu dokładnie wiem jak coś działa, a także mogę w nim tworzyć oprogramowanie komercyjne, bez ~żadnych limitów (a tym się właśnie zajmuję).

Ogólnie jestem zadowolony z tego środowiska, choć wad posiada całkiem sporo. ;)

No i przepisałem na Delphi... Nie działa :(

„Nie działa” to nie jest dobry opis problemu…

Proszę o spojrzenie co jest nie tak...
Zastąpiłem:

  • metodę Canvas.Line na Canvas.MoveTo + Canvas.LineTo,
  • wyrażenie x += y zastąpiłem x:= x+y;

Tu jest dobrze – to tylko inny zapis, działa identycznie.

  • zmienną LLeft zrobiłem globalną, bo w Delphi nie da się lokalnie inicjować zmienne przy deklaracji

Ale po co? Delphi nie umożliwia inicjalizowania zmiennych lokalnych, ale umożliwia ich deklarację. Trzeba było ją zostawić w tym miejscu, a przypisanie wartości początkowej wykonać na początku bloku kodu:

var
  LLeft: Integer;
begin
  LLeft := 0;

Rezultat? Nie rysuje wykresu...

Co to znaczy, że „nie rysuje wykresu”? Niczego nie maluje w komponencie, czy maluje, ale nie wszystko?


Działanie całości najpierw przetestuj na przykładowej serii, tak aby widzieć czy wszystkie metody malujące robią to co trzeba i w taki sposób jak trzeba. Dlatego też wyłącz Timer (tak aby nie odświeżał serii), a serię wypełnij przykładowymi danymi. Czyli metodę ChartSeriesClear napisz np. tak:

procedure TMainForm.ChartSeriesClear();
var
  LVertexIdx: Integer;
begin
  for LVertexIdx := 0 to CHART_DATA_SERIES_SIZE - 1 do
    FChartSeries[LVertexIdx] := 45;
end;

Komponent po włączeniu programu powinien wyglądać mniej więcej tak:

chart.png

Jeśli któregoś elementu wykresu brakuje to przyjrzyj się metodom z prefiksem Print, czyli:

  • PaintChartBackground,
  • PaintChartOutline,
  • PaintChartGridLines,
  • PaintChartPolygonBackground,
  • PaintChartPolygonOutline.

W Delphi malowanie może się odbywać nieco inaczej niż w Lazarusie, choć ten drugi utrzymuje kompatybilność z tym pierwszym, więc wszystko powinno działać prawidłowo (jeśli o klasę TCanvas chodzi).

Jeśli nadal wykres nie jest malowany prawidłowo to użyj debuggera i sprawdź, czy wszystkie metody faktycznie wykonują się. Sprawdź czy Timer działa, czy dane w serii są prawidłowe (wartości z zakresu <0;100>), czy podczas skalowania wartości i współrzędne są prawidłowe.

0

Działa! Jednak problemem była zmienna LLeft, którą zrobiłem globalną (a po co? bo za dużo myślałem :P)
Dzięki jeszcze raz.
-Pawel

0

Cały ten mechanizm warto by było opakować i stworzyć uniwersalny komponent. Dzięki temu mógłbyś mieć w programie kilka różnych wykresów (tak jak w systemowym menedżerze zadań), ale kod napisać tylko raz i móc wygodnie konfigurować kontrolkę z poziomu okna inspektora obiektów.

Ja bym się nad tym długo nie zastanawiał. :]

0

Taki jest plan :P Choć nie całkiem. Myslę bardziej o unicie, który wykorzystam w moim większym projekcie.
Osobiście nie potrzebuję komponentu, bo za dużo pisania.
Ale, skoro już zadałeś sobie tyle trudu, to droga wolna... stwórz komponencik, bo raczej takich, darmowych nie ma.

0
Pepe napisał(a):

Myslę bardziej o unicie, który wykorzystam w moim większym projekcie.

Ale co Ty do unitów chesz surową logikę wydzielać? To sensu nie ma, skoro ta logika dotyczy wyłącznie jednego komponentu. A nawet jeśli przyjdzie czas na kolejny, to wystarczy go przeciągnąć z palety kontrolek i skonfigurować w inspektorze obiektów – minuta roboty, w przeciwieństwie do zabaw z podpinaniem logiki pod PaintBox-y.

Po to właśnie ludzkość wymyśliła komponenty, aby takich zabaw nie urządzać. Kod pisze się raz, daje się wygodę użytkowania i uniwersalizm. Stworzenie kontrolki to jedyne sensowne rozwiązanie.

Osobiście nie potrzebuję komponentu, bo za dużo pisania.

Więcej pisania i ogólnie roboty będziesz miał nie tworząc konkretnej klasy komponentu wykresu – przekonasz się.

Ale, skoro już zadałeś sobie tyle trudu, to droga wolna... stwórz komponencik, bo raczej takich, darmowych nie ma.

Na brak zajęć nie cierpię – wczoraj miałem luźną godzinkę, to przygotowałem prowizorkę, ale ogólnie nie za bardzo mam czas na robienie darmowych komponentów tylko dlatego, że nie ma takich w sieci. Jeśli ktoś będzie potrzebował to z chęcią napiszę – za konkretne wynagrodzenie. ;)

0

Próbuję dostosować wykres do własnych upodobań.
Skalowanie. TPaintBox ma ustawioną właściwość align= alClient.
Wykres musi się dopasować do zmienngo rozmiaru formy (a co za tym idzie kontrolki TPaintBox).
Przyjąłem, że wejściowe dane zawsze są w przedziale 0-100%. Zmieniłem zatem procedurę Timera (CChartUpdateTimerTimer):
Ustawiłem dla testów wartość 50.

procedure TMainFrm.CChartUpdateTimerTimer(Sender: TObject);
var
  LNewValue: Integer;
begin
  LNewValue  := 50; // Random(100); 
  ChartSeriesAddValue(LNewValue);
  PB_Chart.Invalidate;
end;

Przy założeniu, że wykres ma wymary 600x300px -> linia wyrrysowywana jest dokładnie w środku. Jest OK.
Ale, aby wykres był skalowalny, należy zmodyfikować procedurę ChartSeriesToPointsSeries

procedure TMainFrm.ChartSeriesToPointsSeries(const ASource: TChartDataSeries; out ADest: TChartPointsSeries);
var
   LVertexIdx: Integer;
   LLeft: Integer;
   iHighScale: Integer; // zmienna do skali w pionie

begin
   LLeft:= 0;
   // 300px = 3 ,500px = 5, etc...
   iHighScale := Round(PB_Chart.Height / 100); // przeliczenie
   for LVertexIdx := 0 to CHART_DATA_SERIES_SIZE - 1 do
      begin
         ADest[LVertexIdx].Y := PB_Chart.Height - (ASource[LVertexIdx] * iHighScale); // wyrażenie
         ADest[LVertexIdx].X := Min(LLeft, PB_Chart.Width - 1);
         LLeft := LLeft + CHART_SPACING_VERTICES_HORZ;
      end;

   ADest[CHART_DATA_SERIES_SIZE].Y := PB_Chart.Height - 1;
   ADest[CHART_DATA_SERIES_SIZE].X := PB_Chart.Width - 1;

   ADest[CHART_DATA_SERIES_SIZE + 1].Y := PB_Chart.Height - 1;
   ADest[CHART_DATA_SERIES_SIZE + 1].X := 0;
end;

Skalowanie w pionie działa OK.
Pozostało skalowanie w poziomie - i tutaj mam problem, bo nie mam pojęcia którą wartość skalować...
Proszę o podpowiedź. W kodzie ustawiono na sztywno 600px... a powinno być PB_Chart.Width...
-Pawel

0

Mnożnik skali nie może być intem, bo już przed mnożeniem przez daną z serii dostaniesz zaokrąglenie, a tym samym większe końcowe odchylenie. Dlatego też mnożnik powinien być zmiennoprzecinkowy, a owe zaokrąglenie należy przeprowadzić dopiero na końcowym wyniku.

Powinieneś mieć dwa mnożniki – jeden dla poziomu, a drugi dla pionu. Przykład:

var
  LHorzMul, LVertMul: Double;

Na samym początku należy wyznaczyć obie wartości:

LHorzMul := PaintBox.Width / CHART_DATA_SERIES_SIZE;
LVertMul := PaintBox.Height / 100;

Teraz aby wyznaczyć współrzędną X wierzchołka, należy przemnożyć jego indeks w serii przez mnożnik LHorzMul. A do wyznaczenia współrzędnej Y należy przemnożyć wartość z serii przez mnożnik LVertMul. Wynikiem w dalszym ciągu będzie liczba rzeczywista, więc na koniec należy ją zaokrąglić.

ADest[LVertexIdx].X := Round(LHorzMul * LVertexIdx);
ADest[LVertexIdx].Y := PaintBox.Height - Round(LVertMul * ASource[LVertexIdx]);

Może się tak zdarzyć, że funkcja zaokrąglająca będzie miała tutaj kluczowe znaczenie. Jest ich trochę – Round, Int, Trunc, Ceil, Floor. W pewnych przypadkach wynik zaokrąglenia może być niezgodny o 1px z tym oczekiwanym, więc to trzeba potestować.

Choć Round sprawdza się do obliczeń dla wykresów – do swojego go używam i śmiga.

0

No, jesteś szybki i skuteczny :P Działa OK (na pierwszy rzut oka).
Ale, wartość skali w poziomie powinna być zamiast:

LHorzMul := PaintBox.Width / CHART_DATA_SERIES_SIZE;

taka:

LHorzMul := PaintBox.Width / (CHART_DATA_SERIES_SIZE-1);

Wtedy, nie ma zakrzywionej linii po prawej (od wejścia)...
-Pawel

0

Tak sądziłem, że ta linijka:

LHorzMul := PaintBox.Width / CHART_DATA_SERIES_SIZE;

może powodować problem. Seria posiada de facto 61, a nie 60 liczb, więc 1 trzeba odjąć.

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