Wykresy

Z Henryk Dąbrowski
Wersja z dnia 22:36, 18 wrz 2022 autorstwa HenrykDabrowski (dyskusja | edycje) (1 wersja)
(różn.) ← poprzednia wersja | przejdź do aktualnej wersji (różn.) | następna wersja → (różn.)
Przejdź do nawigacji Przejdź do wyszukiwania
05.11.2020



Wprowadzenie

Napisanie kodu, który sporządzi wymagany wykres, jest zwykle nieopłacalne. Zajmuje dużo czasu, a efekt może ciągle wymagać poprawek. Znacznie prostsze i bardziej efektywne jest sporządzenie wykresu przy pomocy gotowych narzędzi dostępnych w LibreOffice. Zatem dlaczego warto poświęcić chwilę czasu, aby poznać podstawy kodowania wykresu? Zauważmy, że gdy już sporządzimy wykres (a często może to być kilka wykresów, które prezentują te same dane w różnym ujęciu), to musimy aktualizować te wykresy w miarę napływania nowych danych – te czynności (często wykonywane codziennie) zbierają dużo czasu i są uciążliwe. Właśnie w takich sytuacjach dobre makro wybawi nas z kłopotu.


Zaczniemy od prostej procedury, która przygotowuje arkusz i dane w tym arkuszu w taki sposób, aby można było je prezentować na wykresie. Dla przykładu wybraliśmy rzeczywiste dane ukazujące liczbę ludności świata, Europy i Afryki w latach 1950 - 2010. Takie dane mogą przydać się nie tylko do prezentowania wykresów, ale również do innych testów oprogramowania. Zauważmy, że kolumny B i D celowo nie zawierają interesujących nas danych. Uczyniliśmy tak dlatego, aby dostęp do danych był utrudniony: zamiast jednego zakresu komórek mamy trzy zakresy komórek wypełnione danymi.

Sub DaneDoWykresu(NazwaArkusza as String)
    Dim oSht as Object
    Dim A()
    If NOT ThisComponent.getSheets().hasByName( NazwaArkusza ) Then 'sprawdza czy arkusz o podanej nazwie już istnieje
        ThisComponent.getSheets().insertNewByName( NazwaArkusza, 0 ) 'wstawia nowy arkusz na pozycję 1 (z indeksem 0)
    End If
    oSht = ThisComponent.getSheets().getByName( NazwaArkusza ) 'uchwyt do arkusza przez nazwę
    ThisComponent.CurrentController.setActiveSheet( oSht ) 'uaktywnienie arkusza
    A = Array( Array( "Year", "", "World", "", "Africa", "Europe" ), _
               Array( 1950, "", 2525, "", 229, 549 ), _
               Array( 1960, "", 3018, "", 285, 606 ), _
               Array( 1970, "", 3682, "", 366, 657 ), _
               Array( 1980, "", 4440, "", 478, 694 ), _
               Array( 1990, "", 5310, "", 632, 721 ), _
               Array( 2000, "", 6127, "", 814, 726 ), _
               Array( 2010, "", 6930, "", 1044, 735 ) )
    oSht.getCellRangeByName( "A1:F8" ).setDataArray( A ) 'wpisujemy przygotowane dane do arkusza
End Sub



Prosty wykres kolumnowy

Przedstawiamy niżej prostą procedurę, która wygeneruje wykres kolumnowy. Uwaga: przy ponownej próbie uruchomienia procedury w ostatniej linii wystąpi błąd, gdy wykres o takiej samej nazwie już istnieje w skoroszycie. Czytelnik może skasować cały arkusz lub co najmniej skasować wygenerowany w tym arkuszu wykres.


Zauważmy też, że musieliśmy wprowadzić trzy różne zakresy komórek, aby uzyskać wykres. Ta liczba jest istotna, gdybyśmy w przyszłości potrzebowali powiększyć obszar wykresu – musimy wtedy powiększyć każdy zakres komórek osobno. Oczywiście uczyniliśmy tak jedynie dla celów demonstracji problemu. Po usunięciu kolumn B i D wszystkie dane byłyby w jednym zakresie komórek.


Dla lepszego zrozumienia działania metody addNewByName Czytelnik powinien w ostatniej linii procedury zmienić ostatni parametr na False:

oCharts.addNewByName( NazwaWykresu, oRect, oAdr(), True, False ) 

Taki przykład pokaże, że dane X (kategorie) zostaną potraktowane jak dane Y, a oś X zostanie ponumerowana kolejnymi liczbami całkowitymi większymi od zera.


Sub Wykresy1()
    Dim NazwaArkusza as String, NazwaWykresu as String
    Dim oSht as Object, oCharts as Object
    Dim oRect as New com.sun.star.awt.Rectangle
    Dim oAdr(2) as New com.sun.star.table.CellRangeAddress
    NazwaArkusza = "Chart1"
    NazwaWykresu = "MyChart1"
    Call DaneDoWykresu( NazwaArkusza ) 'dodaje arkusz (w przypadku braku) i wypełnia danymi do wykresu
    oSht = ThisComponent.getSheets().getByName( NazwaArkusza ) 'uchwyt do arkusza przez nazwę
    ThisComponent.CurrentController.setActiveSheet( oSht ) 'uaktywnienie arkusza
    oAdr(0) = oSht.getCellRangeByName( "A1:A8" ).getRangeAddress() 'dane X (lata)
    oAdr(1) = oSht.getCellRangeByName( "C1:C8" ).getRangeAddress() 'dane Y1 (świat)
    oAdr(2) = oSht.getCellRangeByName( "E1:F8" ).getRangeAddress() 'dane Y2 i Y3 (Afryka i Europa)
    oRect.X = 15000 'pozycja X lewego górnego rogu okna wykresu w setnych milimetra (LINK)
    oRect.Y = 1000 'pozycja Y lewego górnego roku okna wykresu w setnych milimetra
    oRect.Width = 10000 'szerokość okna wykresu
    oRect.Height = 7000 'wysokość okna wykresu
    oCharts = oSht.getCharts() 'uchwyt do wszystkich wykresów w danym arkuszu
    ' przedostatni parametr wskazuje czy nagłówki kolumn są etykietami danych Y
    ' ostatni parametr wskazuje czy pierwsza kolumna danych stanowi kategorie (dane X)
    oCharts.addNewByName( NazwaWykresu, oRect, oAdr(), True, True ) 'dodajemy nowy wykres
End Sub



Modyfikowanie wykresu

Uzyskany wyżej wykres jest bardzo ubogi, a prezentacja danych w kolumnach nie każdemu może odpowiadać. Łatwo możemy uczynić go bardziej czytelnym. Aby uniknąć błędu pojawiającego się, gdy wykres o takiej samej nazwie już istnieje, wprowadziliśmy prostą modyfikację i przed utworzeniem nowego wykresu sprawdzamy, czy przypadkiem wykres o takiej samej nazwie już nie został dodany.

Sub Wykresy2()
    Dim NazwaArkusza as String, NazwaWykresu as String
    Dim oSht as Object
    Dim oCharts as Object, oCrtEmb as Object
    Dim oRect as New com.sun.star.awt.Rectangle
    Dim oAdr(2) as New com.sun.star.table.CellRangeAddress
    NazwaArkusza = "Chart2"
    NazwaWykresu = "MyChart2"
    Call DaneDoWykresu( NazwaArkusza ) 'dodaje arkusz (w przypadku braku) i wypełnia danymi do wykresu
    oSht = ThisComponent.getSheets().getByName( NazwaArkusza ) 'uchwyt do arkusza przez nazwę
    ThisComponent.CurrentController.setActiveSheet( oSht ) 'uaktywnienie arkusza
    oAdr(0) = oSht.getCellRangeByName( "A1:A8" ).getRangeAddress() 'dane X (lata)
    oAdr(1) = oSht.getCellRangeByName( "C1:C8" ).getRangeAddress() 'dane Y1 (świat)
    oAdr(2) = oSht.getCellRangeByName( "E1:F8" ).getRangeAddress() 'dane Y2 i Y3 (Afryka i Europa)
    oRect.X = 15000 'pozycja X lewego górnego rogu okna wykresu w setnych milimetra (LINK)
    oRect.Y = 1000 'pozycja Y lewego górnego roku okna wykresu w setnych milimetra
    oRect.Width = 10000 'szerokość okna wykresu
    oRect.Height = 7000 'wysokość okna wykresu
    oCharts = oSht.getCharts() 'uchwyt do wszystkich wykresów w danym arkuszu
    If NOT oCharts.hasByName( NazwaWykresu ) Then
    '   przedostatni parametr wskazuje czy nagłówki kolumn są nazwami danych Y
    '   ostatni parametr wskazuje czy pierwsza kolumna danych stanowi dane X (kategorie)
        oCharts.addNewByName( NazwaWykresu, oRect, oAdr(), True, True )
    End If
    oCrtEmb = oCharts.getByName( NazwaWykresu ).EmbeddedObject 'uchwyt do wszystkich objektów osadzonych w oknie wykresu
    oCrtEmb.HasMainTitle = True
    oCrtEmb.Title.String = "Ludność świata, Afryki i Europy" 'tytuł główny (tekst)
    oCrtEmb.HasSubTitle = True
    oCrtEmb.Subtitle.String = "w milionach" 'podtytuł (tekst)
    oCrtEmb.HasLegend = True 'legenda (widoczność)
    oCrtEmb.Legend.Alignment = com.sun.star.chart.ChartLegendPosition.RIGHT 'NONE, LEFT, TOP, RIGHT, BOTTOM (LINK)
    oCrtEmb.Legend.FillStyle = com.sun.star.drawing.FillStyle.SOLID 'NONE, SOLID, GRADIENT, HATCH, BITMAP (LINK)
    oCrtEmb.Legend.FillColor = RGB(221, 221, 221) 'kolor tła okienka legendy
    oCrtEmb.Legend.CharHeight = 8 'wysokość czcionki użytej w okienku legendy
    oCrtEmb.Diagram = oCrtEmb.createInstance("com.sun.star.chart.LineDiagram") 'wykres kolumnowy zamieniamy na liniowy
    oCrtEmb.Diagram.Wall.FillColor = RGB(255, 255, 255) 'kolor tła wykresu
End Sub


Wartości parametrów (w przedostatniej linii procedury) i rodzaj generowanego wykresu:

AreaDiagram 'wykres obszarowy (zwykły)
BarDiagram 'wykres kolumnowy (zwykły)
BubbleDiagram 'wykres dymkowy
DonutDiagram 'wykres kołowy (pierścieniowy)
FilledNetDiagram 'wykres siatkowy (wypełniony)
LineDiagram 'wykres liniowy (tylko linie)
NetDiagram 'wykres siatkowy (punkty i linie)
PieDiagram 'wykres kołowy (zwykły)
StockDiagram 'wykres giełdowy (1)
XYDiagram 'wykres punktowy XY (punkty i linie)



Nazwa okna wykresu (wewnętrzna)

Okno wykresu (Chart) ma swoją wewnętrzną nazwę nadaną przez system / procedurę w chwili tworzenia. Możemy też przypisać każdemu z tych okien własną nazwę w polu, do którego dostęp uzyskujemy następująco:

  • lewy klik na oknie wykresu -> prawy klik -> Nazwij...

Pole to można wykorzystać i jeśli poznamy nazwę wewnętrzną nadaną przez system (zawsze postaci Object 1, Object 2 itd.) lub procedurę (w naszym przypadku mieliśmy: MyChart1, MyChart2), to możemy wpisać do tego pola numer obiektu i uzyskać łatwy i trwały dostęp do nazwy okna wykresu. Jeżeli nie nazwaliśmy w powyższy sposób okna wykresu, to dostęp do nazwy wewnętrznej jest możliwy poprzez opcję:

  • Widok -> Nawigator -> Obiekty OLE

Nawigator jest też dostępny pod klawiszem F5. Klikając dwukrotnie lewym przyciskiem myszy, zostaniemy przeniesieni do odpowiedniego arkusza, na którym obiekt o wybranej nazwie zostanie uaktywniony.


Gdyby Czytelnik miał problemy z ustaleniem nazw wewnętrznych okien wykresu, to poniższa procedura przegląda wszystkie arkusze skoroszytu i dla każdego arkusza wpisuje nazwę wewnętrzną okien wykresu jako podtytuł tych okien. Oczywiście należy najpierw sporządzić roboczą kopię pliku i uruchomić procedurę będąc w roboczej kopii pliku. Dzięki temu będziemy mieli łatwy, czytelny i trwały dostęp do nazwy każdego okna wykresu.

Sub NamesOfCharts()
    'wpisuje wewnętrzne nazwy wykresów jako podtytuł wykresu
    Dim oSht as Object, oCrt as Object, oCrtEmb as Object
    Dim i as Long, k as Long
    For i = 0 To ThisComponent.getSheets().getCount() - 1 'przeglądamy wszystkie arkusze
        oSht = ThisComponent.getSheets().getByIndex(i) 'uchwyt do arkusza o indeksie i
        For k = 0 To oSht.getCharts().getCount() - 1 'przeglądamy wszystkie wykresy w danym arkuszu
            oCrt = oSht.getCharts().getByIndex(k) 'uchwyt do wykresu o indeksie k
            oCrtEmb = oCrt.EmbeddedObject 'uchwyt do wszystkich objektów osadzonych w oknie wykresu
            oCrtEmb.HasSubTitle = True
            oCrtEmb.Subtitle.String = oCrt.getName() 'podtytuł (tekst)
        Next k
    Next i
End Sub



Powiększanie zakresu danych pokazywanych na wykresie

Zamieszczona niżej procedura powiększa zakres / zakresy komórek, które stanowią dane wejściowe pokazywane na wykresie. Następuje powiększenie każdego z zakresów komórek o kolejny wiersz (od dołu).


Należy zwrócić uwagę na to, że nie w każdej sytuacji poniższa procedura będzie działała prawidłowo. Rozważmy następujący przypadek: niech będą dane zakresy komórek A1:A10 i C1:C10, gdzie w kolumnie A są dane X, w kolumnie C dane Y, a nagłówki kolumn są etykietami. W tym momencie program widzi dwa zakresy danych C1:C10 (dane Y) i A2:A10 (dane X). Jeżeli teraz będziemy chcieli zaprezentować na wykresie jedynie część danych, powiedzmy od piątego wiersza, to będziemy mieli trzy zakresy danych: C5:C10 (dane Y), A5:A10 (dane X) i C1 (etykieta danych Y). Teraz niżej zamieszczona procedura powiększy trzy zakresy, co zakończy się błędem (etykieta nie może być w dwóch komórkach). Dlatego należy używać jej rozważnie, a najlepiej ustalić i dobrze określić, które elementy z tablicy Zakresy() mają być modyfikowane.


W pewnych sytuacjach zmiana zakresu danych powoduje zmianę formatowania osi X. Zazwyczaj pomaga przywrócenie pierwotnie wybranego formatowania osi X i stąd obecność ostatniej linii w tej funkcji (oznaczonej jako komentarz).

Sub ZwiekszObszarWykresu(NazwaArkusza as String, NazwaWykresu as String)
    'dla wykresu "NazwaWykresu" w arkuszu "NazwaArkusza" powiększa zakres danych
    'dodając kolejny wiersz do wyjściowego zakresu komórek
    Dim oSht as Object, oCrt as Object, Zakresy as Object
    Dim k as Long
    oSht = ThisComponent.getSheets().getByName( NazwaArkusza ) 'uchwyt do arkusza przez nazwę
    oCrt =  oSht.getCharts().getByName( NazwaWykresu )
    Zakresy = oCrt.getRanges()
    For k = LBound(Zakresy) To UBound(Zakresy)
        Zakresy(k).EndRow = Zakresy(k).EndRow + 1
    Next k
    oCrt.setRanges( Zakresy )
    'oCrt.EmbeddedObject.Diagram.XAxis.AxisType = com.sun.star.chart.ChartAxisType.DATE 'AUTOMATIC, CATEGORY, DATE (LINK)
End Sub


Przykładowe wywołanie:

Sub ZmienZakresy()
    Call ZwiekszObszarWykresu( "Arkusz1", "Object 1")
End Sub



Polecane strony internetowe

BasicGuide_OOo3.2.0.pdf

The Structure of Charts

Apache OpenOffice – Interface XTableCharts

Apache OpenOffice – Module chart

ChartAxis Service Reference





LibreOffice Calc – makra                   Strona główna