Wykres punktowy : klasa Plot i płynne API¶
W poprzednim ćwiczeniu powstała biblioteka klas opisujących elementy SVG:
Shape, Line, Circle, Rect, Group, Style oraz
SVGDrawing. Na końcu pokazano także przykład programu, który za pomocą
tych klas rysuje prosty wykres funkcji sinus i cosinus.
Celem tego ćwiczenia jest uporządkowanie tamtego przykładu i zamknięcie jego logiki w dwóch nowych klasach:
Axes— opisującej układ współrzędnych wykresu,Plot— opisującej sam wykres punktowy.
Dodatkowym celem ćwiczenia jest zastosowanie płynnego API
(fluent API) podczas konfiguracji obiektu Axes.
Zadanie¶
Napisz moduł plot.py zawierający klasy Axes oraz Plot.
Nowe klasy mają korzystać z wcześniej przygotowanej biblioteki SVG.
W szczególności klasa Axes ma być również obiektem rysowalnym, czyli
powinna dziedziczyć po klasie Shape i implementować metodę draw().
Klasa Axes¶
Klasa Axes ma opisywać prostokątny obszar wykresu oraz sposób mapowania
wartości numerycznych na współrzędne ekranu. Konstruktor klasy powinien przyjmować:
identyfikator obiektu,
współrzędne prostokąta rysowania:
x0, y0, x1, y1.
Przykład:
ax = Axes("ax1", 80, 40, 580, 260)
Klasa ma udostępniać metody konfiguracyjne w stylu płynnego API, tzn.
każda z nich powinna zwracać self.
Wymagane metody:
bottom(xmin, xmax)top(xmin, xmax)left(ymin, ymax)right(ymin, ymax)with_xticks(n)with_yticks(n)with_stroke(color)with_stroke_width(width)
Mile widziana (choć nie wymagana) jest też metoda with_grid(), która umożliwi dodanie
linii siatki do wykresu.
Przykład użycia:
axes = (
Axes("ax1", 80, 40, 580, 260)
.bottom(0.0, 360)
.left(-1.1, 1.1)
.top(0.0, 360)
.right(-1.1, 1.1)
.with_xticks(6)
.with_yticks(4)
.with_grid(True)
.with_stroke("black")
.with_stroke_width(1.5)
)
Klasa Axes powinna także udostępniać metody przeliczające współrzędne
z układu danych do układu ekranu:
map_x(x)map_y(y)
Metoda draw() powinna zwracać fragment kodu SVG rysujący:
ramkę wykresu,
opcjonalną siatkę, jeśli została uwzględniona w implementacji.
znaczniki osi, jeśli zostały uwzględnione w implementacji.
Nie trzeba rysować napisów, etykiet ani legendy.
Klasa Plot¶
Klasa Plot ma być prostym opakowaniem nad obiektem SVGDrawing.
Konstruktor:
pl = Plot(axes)
gdzie axes jest obiektem klasy Axes.
Klasa Plot powinna:
przechowywać obiekt
Axes,utworzyć obiekt
SVGDrawing,dodać do rysunku obiekt
Axes,umożliwić nanoszenie punktów metodą
scatter(),umożliwić zapis wyniku metodą
save_fig().
Wymagana metoda:
scatter(x, y, marker="o", style=None)
Metoda scatter() powinna:
przyjąć dwie listy liczb:
xiy,sprawdzić, czy obie listy mają tę samą długość,
przeliczyć współrzędne punktów za pomocą metod
map_x()imap_y(),utworzyć znaczniki odpowiedniego typu,
dodać je do rysunku jako grupę SVG.
Zakładamy, że obsługa markerów została wydzielona do osobnego modułu
(np. przez klasę MarkerFactory), dlatego metoda scatter()
powinna umieć korzystać z parametru marker.
Przykładowe znaczniki:
"o"— koło,"d"— romb.
Uwagi¶
Axesma dziedziczyć poShape.Każda metoda płynnego API ma zwracać
self.Obiekt
Axesma sam umieć się narysować.Obiekt
Plotma korzystać zSVGDrawing, a nie zastępować go.Wykres ma być wykresem punktowym.
Styl punktów ma być przekazywany obiektem klasy
Style.Wynik ma zostać zapisany do pliku SVG.
Ćwiczenie to jest bezpośrednim rozwinięciem poprzedniego przykładu,
w którym wykres był budowany „ręcznie” z obiektów Circle, Line,
Group i SVGDrawing. Teraz ta sama logika ma zostać zamknięta
w klasach wyższego poziomu.
Program testowy¶
Końcowa implementacja musi poprawnie działać z poniższym kodem:
import math
from plot import Axes, Plot
from svg_shapes import Style
axes = (
Axes("ax1", 80, 40, 580, 260)
.bottom(0.0, 360)
.left(-1.1, 1.1)
.top(0.0, 360)
.right(-1.1, 1.1)
.with_xticks(6)
.with_yticks(4)
.with_grid(True)
.with_stroke("black")
.with_stroke_width(1.5)
)
x = [i for i in range(0, 370, 10)]
ys = [math.sin(v*3.14159/180.0) for v in x]
yc = [math.cos(v*3.14159/180.0) for v in x]
pl = Plot(axes)
sin_style = Style(fill="#a6cee3", stroke_width=1.5, stroke="#1f78b4")
cos_style = Style(fill="#fdbf6f", stroke_width=1.5, stroke="#ff7f00")
pl.scatter(x, ys, marker="o", style=sin_style)
pl.scatter(x, yc, marker="d", style=cos_style)
pl.save_fig("fluent_plot.svg")
Wynik powinien przypominać wykres z poprzedniego ćwiczenia: ma zawierać ramkę, (opcjonalnie) siatkę oraz dwa zbiory punktów odpowiadające funkcjom sinus i cosinus, narysowane różnymi stylami.