MATLAB.Exponenta
–Û·Ë͇ Matlab&Toolboxes

Приложения с GUI и дескрипторная графика

Разные вопросы по графике

Построение плоскостей, конусов, эллипсоидов и других поверхностей

Поверхность может быть задана уравнением

z = ƒ(x,y),

или в более общем виде

g(x,y,z) = 0.

Плоскость построить проще всего, обратившись к одному из способов ее задания. Например, уравнение плоскости, пересекающей ось Ox в точке (α,0,0), ось Oy в точке (0,b,0) и ось Oz в точке (0,0,c) имеет вид

Выражаем отсюда z, задаем пределы по Ox и Oy, сетку на этой прямоугольной области (функцией meshgrid) и строим поверхность (при помощи одной из функций mesh, surf, или других).

a=-2;
b=3;
c=2;
[X,Y]=meshgrid(-5:0.5:5, -4:0.5:4);
Z=c*(1-X/a-Y/b);
figure('Color','w')
hS=mesh(X,Y,Z);
xlabel('x');
ylabel('y');
zlabel('z')

Указатель на поверхность hS можно затем использовать для изменения свойств поверхности, например, залить ее каким-либо цветом и задать цвет линий сетки поверхности.

set(hS,'FaceColor','g','EdgeColor','k')

Если требуется построить плоскость, нормальный вектор к которой (исходящий из начала координат) имеет длину p, и образует углы αx, αy и αz с осями Ox, Oy и Oz, соответственно, то уравнение плоскости имеет вид:

xcosαx + ycosαy + zcosαz - p = 0.

Для построения плоскости, проходящей как через заданную точку трехмерного пространства с координатами (x1, y1, z1), так и с заданным направлением вектора нормали к плоскости, проекции которого на оси Ox, Oy и Oz равны, соответственно nx, ny и nz, служит следующее уравнение

nx(x - x1) + ny(y - y1) + nz(z - z1) = 0.

Плоскость, проходящая через три заданные точки с координатами , и задается следующим уравнением

(Для вычисления определителя в MATLAB есть функция det).

Однако, для построения многих поверхностей проще воспользоваться параметрическим способом их задания:

x = x(u,v), y = y(u,v), z = z(u,v)

где параметры u и v меняются некоторым подходящим образом.

Например, для построения поверхностей второго порядка удобно обратиться к их параметрическим заданиям (см. также раздел Построение графиков функций на непрямоугольной области определения (функции ezsurf, ezmesh)).

Эллипсоид определяется следующей зависимостью координат точек своей поверхности от двух параметров u и v:

x = a sin u cos v, y = b sin u cos v, z = c sin u cos v

Сначала мы сгенерируем вектор-столбец u и вектор-строку v, содержащие значения параметров, т.е. прямоугольная сетка точек на области изменения параметров, в которых будут вычисляться значения функций x = x(u,v), y = y(u,v), z = z(u,v), определяется на прямоугольной области изменения параметров

[min(u), max(u)] x [min(v), max(v)]

Тогда операторы

X = a*sin(u)*cos(v); Y = b*sin(u)*sin(v);

будут законны, поскольку вектор sin(u) того же размера, что и вектор-столбец u, а размер cos(v) совпадает с размером вектор-строки v. В результате матричных умножений sin(u)*cos(v) и sin(u)*sin(v) получаются матрицы одинаковых размеров. Несколько сложнее дело обстоит с вычислением на сетке параметров, поскольку сюда входит только один параметр, значения которого содержатся в вектор-столбце u. Написать Z = c*cos(u) было бы неверно, т.к. для построения поверхности должны получиться матрицы X, Y и Z одинаковых размеров. Несложно понять, что надо в матрице Z сделать одинаковые столбцы и число их должно совпадать с длиной вектора v, для чего можно воспользоваться следующим присваиванием:

Z = c*cos(u)*ones(size(v));

Пример построения эллипсоида

a=4;
b=2;
c=3;
u = (0:0.05*pi:2*pi)';
v = [0:0.05*pi:2*pi];
X = a*sin(u)*cos(v);
Y = b*sin(u)*sin(v);
Z = c*cos(u)*ones(size(v));
figure('Color','w')
hS=mesh(X,Y,Z);
xlabel('x'); ylabel('y'); zlabel('z')

эллипсоид

Ясно, что если изменять u и v не от нуля до 2, а скажем до 3/2, то получится эллипсоид без части поверхности.

Однополостный гиперболоид определяется следующей зависимостью координат точек поверхности от двух параметров u и v:

x = a ch u cos v, y = b ch u sin v, z = c sh u

Для его построения применяется тот же самый подход, что и описанный выше для эллипсоида.

a=4;
b=2;
c=3;
u = (-1:0.05:1)';
v = [0:0.05*pi:2*pi];
X = a*cosh(u)*cos(v);
Y = b*cosh(u)*sin(v);
Z = c*sinh(u)*ones(size(v));
figure('Color','w')
hS=mesh(X,Y,Z);
xlabel('x'); ylabel('y'); zlabel('z')

Однополостный гиперболоид

Двуполостный гиперболоид определяется следующей зависимостью координат точек поверхности от двух параметров u и v:

x = ± a ch u, y = b sh u sin v, z = c sh u cos v,

где знаки + и - соответствуют двум полостям. Для его построения применяется тот же самый подход, что и описанный выше для эллипсоида, только теперь при вычислении матрицы X вектор-столбец cosh(u) умножается на состоящую из единиц вектор-строку, длина которой совпадает с v. После построения одной полости изменяется знак элементов матрицы X и достраивается вторая полость.

a=4;
b=2;
c=3;
u = (0:0.05:2)';
v = [0:0.05*pi:2*pi];
X = a*cosh(u)*ones(size(v));
Y = b*sinh(u)*sin(v);
Z = c*sinh(u)*cos(v);
figure('Color','w')
hS1=mesh(X,Y,Z);
hold on
X=-X;
hS2=mesh(X,Y,Z);
xlabel('x'); ylabel('y'); zlabel('z')

Двуполостный гиперболоид

Эллиптический параболоид определяется следующей зависимостью координат точек поверхности от двух параметров u и v:

x = a u cos v, y = b u cos v,

Его построение ничем не отличается от построения эллипсоида, только при вычислении надо использовать поэлементное возведение в квадрат для вектора u.

a=4;
b=2;
u = (0:0.05:2)';
v = [0:0.05*pi:2*pi];
X = a*u*cos(v);
Y = b*u*sin(v);
Z = 0.5*u.^2*ones(size(v));
figure('Color','w')
hS1=mesh(X,Y,Z);
xlabel('x'); ylabel('y'); zlabel('z')

Эллиптический параболоид

Гиперболический параболоид определяется следующей зависимостью координат точек поверхности от двух параметров u и v:

x = a u ch v, y = b u ch v,

Его построение ничем не отличается от построения эллиптического параболоида, которое было описано выше

a=4;
b=4;
u = (-2:0.05:2)';
v = -2:0.05*pi:2;
X = a*u*cosh(v);
Y = b*u*sinh(v);
Z = 0.5*u.^2*ones(size(v));
figure('Color','w')
hS1=mesh(X,Y,Z);
xlabel('x'); ylabel('y'); zlabel('z')

Гиперболический параболоид

Действительный конус определяется следующей зависимостью координат точек поверхности от двух параметров u и v:

x = a u cos v, y = b u sin v, z = cu

a=4;
b=2;
c=3;
u = (-2:0.1:2)';
v = [0:0.05*pi:2*pi];
X = a*u*cos(v);
Y = b*u*sin(v);
Z = c*u*ones(size(v));
figure('Color','w')
hS=mesh(X,Y,Z);
xlabel('x'); ylabel('y'); zlabel('z')

Действительный конус

Эллиптический цилиндр определяется следующей зависимостью координат точек поверхности от двух параметров u и v:

x = a cos v, y = b sin v, z = u

и строится точно так же, как и конус

a=4;
b=2;
u = (-2:0.1:2)';
v = [0:0.05*pi:2*pi];
X = a*ones(size(u))*cos(v);
Y = b*ones(size(u))*sin(v);
Z = u*ones(size(v));
figure('Color','w')
hS=mesh(X,Y,Z);
xlabel('x'); ylabel('y'); zlabel('z')

Эллиптический цилиндр

Еще один способ построения поверхностей и графиков функций с непрямоугольной областью определения описан в следующем разделе.

Построение графиков функций на непрямоугольной области определения (функции ezsurf, ezmesh)

Функции ezsurf и ezmesh очень удобны для построения функций, область определения которых не является прямоугольником, поскольку область определения находится этими функциями автоматически. Кроме того, их можно использовать и для построения параметрически задаваемых поверхностей, описанных в разделе Построение плоскостей, конусов, эллипсоидов и других поверхностей.

Еще один способ построения графиков на непрямоугольной области определения заключается в создании подходящей треугольной сетки на плоскости при помощи функций delaunay и вызова функции trimesh или trisurf (см. разд. Триангуляция Делоне).

Функции ezsurf и ezmesh допускаю достаточно много способов обращения к ним, отличие состоит только в том, что ezsurf строит залитую цветом поверхность, а ezmesh - каркасную поверхность.

Самый простой вариант вызова функций ezsurf или ezmesh - указание строки с выражением для функции ƒ = ƒ(x,y). Тогда функция строится на прямоугольной области определения x,y[-2,2], например:

ezsurf('sqrt(x^2+y^4)')

Функции ezmesh и ezsurf не требуют указания поэлементных операции в отличие от использования surf и mesh, в которых поэлементные операции применяются для поэлементного перемножения, деления и возведения в степень массивов одинаковых размеров, которые задают прямоугольную сетку на области построения функции.

Функции ezsurf и ezmesh сами подписывают оси и размещают вверху осей формулу, по которой определяется функция (о выводе математических формул в графическое окно с использованием интерпретаторов TeX и LaTeX написано в разделе Вывод математических формул, смена шрифта и начертания).

Для задания прямоугольной области определения функции достаточно вызвать ezsurf или ezmesh со вторым входным аргументом - вектором из четырех элементов [xmin, xmax, ymin, ymax] или вектором из двух элементов [a, b], тогда будет считаться, что a < x, y < b.

Если функция определена не во всех точках прямоугольной области, то график все равно построится верно, например при построении графика функции при помощи

figure('Color','w')
ezsurf('sqrt(x^2-y^4)')

получается следующий график


График функции

Если поверхность задана параметрическими функциями x = x(u,v), y = y(u,v) и z = z(u,v), то их так же можно указывать во входных аргументах ezsurf или ezmesh, например

figure('Color','w')
ezsurf('4*sin(u)*cos(v)', '2*sin(u)*sin(v)', '3*cos(u)')

приводит к построению эллипсоида (правда, не совсем корректному из-за неверных границ изменения параметров u и v)


Эллипсоид, построенный при помощи ezsurf

По умолчанию, значения параметров u и v изменяются от -2 до 2. Для задания других границ для параметров следует вызвать функцию ezsurf или ezmesh с четвертым входным аргументом - вектором из четырех элементов [umin, umax, vmin, vmax] или вектором из двух элементов [a, b], тогда будет считаться, что a < u, v < b.

В предыдущем примере достаточно было менять значения параметров от 0 до 2, т.е. обратиться к ezsurf так

figure('Color','w')
ezsurf('4*sin(u)*cos(v)', '2*sin(u)*sin(v)', '3*cos(u)', [0, 2*pi])

тогда поверхность не будет нарисована дважды. Очевидно, что можно построить часть эллипсоида, выбрав меньшие интервалы, на которых изменяются параметры.

Для изменения сетки следует указать в качестве последнего аргумента число ее узлов (по умолчанию сетка 60 на 60 узлов), например

figure('Color','w')
ezsurf('4*sin(u)*cos(v)', '2*sin(u)*sin(v)', '3*cos(u)', [0, 2*pi], 20)

приводит к менее гладкой поверхности


Уменьшение узлов сетки

При построении семейства поверхностей (каждая из которых определяется некоторым параметром, входящим в задающие поверхность формулы) обычно требуется передать в ezmesh или ezsurf значения параметров. Предположим, что необходимо построить семейство эллиптических параболоидов, задаваемых при помощи параметрической зависимости

x = u cos v, y = u sin v, + p

где p = 0,1,...,5. Конечно, можно шесть раз вызвать ezsurf:

ezsurf('u*cos(v)','u*sin(v)','0.5*u^2',[0 2], [0, 2*pi])
hold on
ezsurf('u*cos(v)','u*sin(v)','0.5*u^2+1',[0 2], [0, 2*pi])
ezsurf('u*cos(v)','u*sin(v)','0.5*u^2+2',[0 2], [0, 2*pi])
и т.д.

но значений параметров может быть и больше. Есть способ лучше - осуществить вызов ezsurf в цикле for по значениям параметров. Для этого следует задать зависящие от параметров функции как анонимные. Анонимная функция, например для вычисления ƒ(u,v) = u + v² определяется следующим образом:

>> f=@(u,v)u+v^2;

т.е. после знака @ в круглых скобках пишутся имена аргументов, а затем выражение для функции. Теперь ей можно пользоваться, например

>> f(2,3)
ans =
    11

Если в выражение для анонимной функции входит некоторая переменная, которая не была объявлена в списке ее входных аргументов, то при вычислении анонимной функции подставляется текущее значение этой переменной.

В нашем примере в качестве анонимной функции достаточно задать только функцию + p, причем при использовании анонимных функций следует применять поэлементные операции, т.к. это ускоряет вычисления. Анонимная функция будет такая @(u,v)0.5*u.^2+p

figure('Color','w')
axes
hold on
for p=0:5
    ezsurf('u*cos(v)','u*sin(v)',@(u,v)0.5*u.^2+p, [0 2], [0, 2*pi])
end
view(3)
axis([-2 2 -2 2 0 7])

Команды после цикла задают нужные пределы трехмерным осям и разворачивают их в стандартное положение. В результате получается:


Семейство эллиптических параболоидов, построенное при помощи ezsurf

Добавление графиков к графикам, нарисованным plotyy

Функция plotyy применяется для визуализации функций с сильно отличающимися значениями и выводит две вертикальные оси y с разными масштабами, например при построении графиков функций ƒ = sin(x) и g = 10cosx получается следующие графики

x=-5:0.1:5;
f=sin(x);
g=10*cos(x);
figure
plotyy(x,f,x,g)

Так получается потому, что в реальности в графическом окне размещаются две пары осей, но цвет заливки оси с графиком функции ƒ = sin(x) белый, а вторые оси того же самого размера с графиком g = 10cosx прозрачные.

Проблема возникает при попытке добавления графика функции h = 10sin²x на оси, которые содержат график g = 10cosx. Если выполнить вычислить функцию h.

h=10*sin(x).^2; 

затем выполнить команды

hold on 
plot(x,h)

то график функции h добавится на оси, на которых находится график ƒ = sin(x) и получится плохо:

Для решения задачи о выборе осей следует с самого начала вызвать функцию plotyy с тремя выходными аргументами

[hA,hL1,hL2]=plotyy(x,f,x,g)

В этом случае в вектор hA записываются указатели на две пары осей, причем hA(1) содержит указатель на оси с графиком функции ƒ = sin(x), а hA(2) содержит указатель на оси с графиком функции g = 10cosx (про указатели на графические объекты написано в разделе Текущий графический объект; указатели на объекты).

Далее, перед построением следующего графика нужная пара осей делается текущей при помощи функции axes (во входном аргументе axes ставится указатель на вторую пару осей, т.е. hA(2)) и добавляется график. В нашем случае нужная пара осей - вторая. Следующие команды, выполненные после [hA,hL1,hL2]=plotyy(x,f,x,g):

axes(hA(2)) 
hold on 
hL3=plot(x,h);

приводят к правильному результату.

Однако, если теперь добавить график, например функции на первую пару осей, сделав ее предварительно текущими, т.е. выполнить

axes(hA(1)) 
hold on
hL4=plot(x,x)

то будут скрыты графики на вторых осях, т.к. они закрылись первыми осями белого цвета. Поэтому, после выбора первой пары осей и построения на них графиков следует сделать текущими вторую пару осей, т.е. выполнить команду

axes(hA(2))

При следующих переключениях между осями и добавлениях графиков на разные оси команда hold on не нужна (если она была один раз выполнена для каждой из пар осей).

Редкая сетка на гладкой поверхности

Часто возникает задача вывода поверхности, на которой нанесена прямоугольная сетка. Конечно, функции mesh и surf это и делают, однако, шаг сетки на поверхности совпадает с шагом сетки, в узлах которой вычисляются значения задающей поверхность функции. Если требуется построить достаточно гладкую поверхность, то и линии сетки на ней будут слишком часто, например при построении поверхности графика функции на прямоугольной области определения x[-1,1], y[-2,2] с шагом 0.01 как по x, так и по y при помощи команд

[X,Y]=meshgrid(-1:0.01:1,-2:0.01:2); 
Z=exp(-X.^2-Y.^2).*X.*Y; 
surf(X,Y,Z)

получается не очень хорошо


Слишком частая сетка

При выборе большего шага сетки на области определения x[-1,1], y[-2,2], скажем 0.2 как по x, так и по y

[X,Y]=meshgrid(-1:0.2:1,-2:0.2:2); 
Z=exp(-X.^2-Y.^2).*X.*Y; 
surf(X,Y,Z)

получается негладкая поверхность с редкой сеткой


Редкая сетка, но и поверхность негладкая

Для решения этой проблемы можно воспользоваться следующим приемом. Сначала построить поверхность на сетке с малым шагом, а сами линии сетки сделать невидимые, за видимость (и цвет) линий сетки отвечает свойство EdgeColor поверхности, его значение 'none' приводит к скрытию линий сетки поверхности

[X,Y]=meshgrid(-1:0.01:1,-2:0.01:2); 
Z=exp(-X.^2-Y.^2).*X.*Y; 
surf(X,Y,Z,'EdgeColor','none') 

Далее надо самостоятельно добавлять линии сетки, для чего выполняется команда hold on

hold on 

Определяем размер сетки, например по матрице X

[m,n]=size(X); 

Выбираем, скажем, каждый 20-ый столбец из матриц X, Y, Z и строим соответствующие трехмерные линии черного цвета при помощи функции plot3, при этом проходим как по столбцам массивов X, Y и Z, так и по строкам, чтобы получить линии вдоль обеих осей

i=1:20:n 
plot3(X(:,i),Y(:,i),Z(:,i),'k') 
j=1:20:m 
plot3(X(j,:)',Y(j,:)',Z(j,:)','k') 
hold off

В результате получается плавная поверхность с редкой сеткой


Гладкая поверхность с редкой сеткой

Управление линиями уровня контурных графиков

Функции двух переменных часто визуализируют при помощи линий уровня. Самый простой способ состоит в использовании функции contour, которая строит линии уровня. Функция contourf делает то же самое, что и contour, но дополнительно заливает промежутки между линиями уровня цветом, зависящем от значения функции. Например, линии уровня функции на прямоугольной области определения x[-1,1], y[-2,2] можно получить следующим образом:

[X,Y]=meshgrid(-1:0.01:1,-2:0.01:2); 
Z=exp(-X.^2-Y.^2).*X.*Y; 
figure('Color','w')
contour(X,Y,Z)
figure('Color','w')
contourf(X,Y,Z)

При этом значения функции, которые отображаются линиями уровня, выбираются автоматически


Линии уровня (слева) и залитые цветом линии уровня (справа)

Гораздо лучше сразу определить, сколько должно быть линий, или для каких значений функции их следует строить. Для этого надо вызвать функции contour или contourf со вторым выходным аргументом - числом линий уровня n, или вектором значений функции v, для которых следует рисовать линии уровня:

contour(X,Y,Z,n), или contour(X,Y,Z,v), или contourf(X,Y,Z,n), или contourf(X,Y,Z,v).

Можно также изменить цвет и стиль контурных линий, например:

contour(X,Y,Z,30,'r')

строит 30 красных линий уровня (сокращения для других цветов см. предопределенные цвета).

Для размещения рядом с каждой линией уровня ярлыка с соответствующим значением функции следует вызвать contour или contourf с двумя выходными аргументами, в которых записывается информация о положении линий уровня (то же самое делает и функция contourc) и указатель на рисованный объект contourgroup (про рисованные объекты в MATLAB 7 написано в разделе Рисованные объекты). Далее для размещения ярлыков с подписями достаточно вызвать функцию clabel:

figure('Color','w')
[C,h]=contour(X,Y,Z);
clabel(C,h)

В результате получается


Подписанные линии уровня (contour + clabel)

Аналогично расставляются ярлыки со значениями функции и для линий уровня, промежутки между которыми залиты цветами. В этом случае надо только использовать функцию contourf в сочетании с функцией clabel.

Рассмотрим теперь способы управления получающимися линиями уровня и подписями к ним. Во-первых, можно задавать свойства рисованного объекта contourgroup прямо при его создании. Например, если требуется сделать фон ярлычков со значениями функции желтым, то достаточно указать, что свойство BackgroundColor текстовых объектов (т.е. ярлыков с подписями) должно принимать значение 'y', т.е. желтый цвет (другие цвета перечислены в разд. предопределенные цвета):

figure('Color','w')
[C,h]=contour(X,Y,Z);
clabel(C,h,'BackgroundColor','y')


Изменение цвета ярлыков при помощи свойства BackgroundColor текстовых объектов

Подробно про свойства текстовых объектов написано в разделе Текстовый объект, вывод текста и математических формул в графическое окно http://matlab.exponenta.ru/forum/gui/book1/grbase.php#2.

Схожим образом можно изменять и другие свойства текста в ярлыках, например, для того, чтобы сделать цвет шрифта красный, а сам шрифт жирный в уже существующем графике линий уровня, достаточно при помощи функции set установить ему нужные свойства. Более корректно сначала выделить текстовые объекты, входящие в contourgroup, а затем изменять их свойства. Указатель на график с линиями уровня, т.е. на объект contourgroup, находится в переменной h, значение которой в предыдущем примере вернула функция contour. Ярлыки с подписями, т.е. текстовые объекты являются потомками объект contourgroup. Для того, чтобы найти их, достаточно использовать функцию findobj, которая ищет потомков некоторого объекта по заданным значениями его свойств. Например, команды

ht=findobj(h,'Type','text')
set(ht,'Color','r','FontWeight','bold')

приводят к записи в вектор ht указателей на текстовые объекты (их свойство 'Type' всегда принимает значение 'text') и изменении цвета текста на красный, и начертания шрифта на жирный, что дает следующий график линий уровня:


Изменение свойств ярлыков при помощи других свойств текстовых объектов

Указатели на текстовые объекты, входящие в состав объекта contourgroup можно получить и при помощи функции clabel;

ht=clabel(C,h,'BackgroundColor','y')

возвращает вектор указателей на соответствующие текстовые объекты.

Если требуется полный доступ к свойствам линий уровня, то указатели на них можно найти при помощи

hp=findobj(h,'Type','patch')

т.к. линии уровня созданы при помощи полигональных объектов, а их свойство 'Type' всегда принимает значение 'patch'.

Сам объект contourgroup имеет ряд свойств для глобальных настроек его вида. Перечислим некоторые из них (значение им присваивается при помощи функции set, первый входной аргумент которой - указатель на объект contourgroup, а дальше через запятую идут пары 'НазваниеСвойства', значение):

Fill - заполнять или нет цветом промежутки между линиями уровня, значения

  • 'off' - не заполнять (по умолчанию);
  • 'on' - заполнять.

LabelSpacing - расстояние между ярлыками, задается в пунктах, по умолчанию 144пт.

LevelStep - шаг по значениям функции, которые надо отобразить линиями уровня, значением является число.

LineColor - цвет линий уровня, значения

  • 'auto' - цвет линий выбирается автоматически (по умолчанию);
  • один из предопределенных цветов, или вектор, задающий цвет в формате RGB (см. предопределенные цвета http://matlab.exponenta.ru/forum/gui/book1/predefcol.php);
  • ' none' - не рисовать линии уровня.

LineStyle - стиль линий уровня, значения - один из предопределенных стилей http://matlab.exponenta.ru/forum/gui/book1/predeflines.php

LineWidth - толщина линий уровня в пунктах, значение - число.

ShowText - показывать или нет ярлыки с подписями, значения:

  • 'on' - показывать;
  • 'off' - не показывать (по умолчанию).

TextStep - интервал по значениями функции, к линиям уровня которых надо ставить ярлыки, значение - число.

Пример изменения свойств контурных графиков

В этом примере строятся линии уровня, дальше изменяются свойства объекта contourgroup:

  • толщина линий уровня задается 1пт;
  • шаг по значениям функции, которые рисуются линиями уровня, берется равным 0.025;
  • ярлыки со значениями ставятся для линий уровня с большим в два раза шагом, т.е. 0.05.
figure('Color','w')
[C,h]=contour(X,Y,Z);
clabel(C,h)
set(h,'LineWidth',1,'LevelStep',0.025, 'TextStep',0.05)

В результате получается


Объект contourgroup с измененными свойствами



Поиск по сайту:

Система Orphus

Яндекс.Метрика