{ "cells": [ { "cell_type": "markdown", "id": "b49ea582", "metadata": {}, "source": [ "# Aula5.Ex - *Scalable Vector Graphics*\n", "O *Scalable Vector Graphics* é um padrão para representação de gráficos vetoriais. Como tem apenas a descrição geométrica para o conjunto de vértices e as relações entre eles, em geral é menor do que os arquivos que representam a imagem como uma matriz de *bytes*.\n", "## Exemplos com Primitivas\n", "Para criar uma linha, usamos a primitiva `line` e definimos as posições `x1`, `y1` do início da linha e as posições `x2`, `y2` do fim da linha. Usamos ainda o atributo `stroke-width` para a largura da linha e `stroke` para a cor." ] }, { "cell_type": "code", "execution_count": null, "id": "6426a202", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", "" ] }, { "cell_type": "markdown", "id": "4eb62d53", "metadata": {}, "source": [ "Para a construção de um retângulo, usamos a posição inicial em `x` e `y`, as dimensões `width` e `height` e a cor do preenchimento `fill` (representada como código hexadecimal `#FFFF00`)." ] }, { "cell_type": "code", "execution_count": null, "id": "3ea26d69", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", "" ] }, { "cell_type": "markdown", "id": "d4a77571", "metadata": {}, "source": [ "Podemos representar um texto com tamanho `font-size`." ] }, { "cell_type": "code", "execution_count": null, "id": "919cc1b8", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " um texto\n", "" ] }, { "cell_type": "markdown", "id": "234972d5", "metadata": {}, "source": [ "Para desenhar mais de uma figura, basta passar mais de um comando para a função `svg`, isto é, mais de uma *tag*, como `rect` e `ellipse`, por exemplo. Neste caso, as figuras são desenhadas na ordem em que são descritas, ou seja, a que for definida por último será desenhada em cima da anterior." ] }, { "cell_type": "code", "execution_count": null, "id": "a0032685", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", " \n", "" ] }, { "cell_type": "markdown", "id": "26a8a786", "metadata": {}, "source": [ "A primitiva `path` permite desenhar uma linha contínua a partir de um conjunto de direções, listado em `d`. Partindo da posição inicial `M` (a vírgula é opcional, cada par de coordenadas é considerado uma posição em `(x, y)`), desenha-se um conjunto de retas, definido por `L`, passando por cada ponto descrito por cada par de coordenadas no caminho. Por padrão, presume-se que o caminho forme um objeto. O preenchimento `none` evita que este seja visível, exibindo apenas as linhas que seriam seu contorno." ] }, { "cell_type": "code", "execution_count": null, "id": "971d02fd", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", "" ] }, { "cell_type": "markdown", "id": "97a4315d", "metadata": {}, "source": [ "É possível combinar as primitivas `line` e `path` para desenhar uma figura utilizando ambos os métodos. Assim, para desenhar duas retas, cada uma com uma primitiva, tem-se:" ] }, { "cell_type": "code", "execution_count": null, "id": "72caa7e0", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", " \n", "" ] }, { "cell_type": "markdown", "id": "cf0da1be", "metadata": {}, "source": [ "Em geral, `path` é usado para figuras mais complexas, que não podem ser facilmente compostas com as outras primitivas. Neste caso, é desenhada uma curva `C` (indicando uma cúbica), com os pares de coordenadas seguintes sendo seus pontos de controle, seguida de uma linha `L`, partindo do fim desta curva. Como todo o conjunto de direções está dentro de um mesmo `d`, a curva e a reta formam a mesma linha." ] }, { "cell_type": "code", "execution_count": null, "id": "055b5a3a", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", "" ] }, { "cell_type": "markdown", "id": "0970c11f", "metadata": {}, "source": [ "Conforme descrito, a primitiva `line` pode ser usada para desenhar retas. Omitir o atributo `stroke` faz com que, na maioria dos navegadores, a reta não seja desenhada (e tenha um comportamento anormal nos que aparece)." ] }, { "cell_type": "code", "execution_count": null, "id": "78b98342", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", " \n", " \n", " \n", "" ] }, { "cell_type": "markdown", "id": "8602bc3e", "metadata": {}, "source": [ "Podemos gerar várias retas com atributos específicos para obter efeitos interessantes. Aqui, são criados dois pares de retas de mesma largura e cada um ocupando uma posição. Para cada par, um atributo `stroke-dasharray` determina a proporção na qual a reta será interrompida, permitindo ver a de trás. A reta mais ao fundo da imagem tem as extremidades arredondadas, determinadas pelo valor do atributo `stroke-linecap`." ] }, { "cell_type": "code", "execution_count": null, "id": "59484ca1", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", " \n", " \n", " \n", "" ] }, { "cell_type": "markdown", "id": "b377298b", "metadata": {}, "source": [ "Conforme visto no exemplo anterior, a ordem das declarações das primitivas define como serão desenhadas. Assim, pode-se obter resultados mais complexos simplesmente definindo a ordem na qual as primitivas são desenhada. Tomando, por exemplo, um conjunto de retângulos:}" ] }, { "cell_type": "code", "execution_count": null, "id": "24783893", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "" ] }, { "cell_type": "markdown", "id": "e2bf1943", "metadata": {}, "source": [ "O atributo `stroke-dasharray`, usado para desenhar de forma espaçada, não é restrito apenas à primitiva `line`. Assim como todas as primitivas têm uma cor definida por `stroke` e largura por `stroke-width`, também pode-se usar `stroke-dasharray` para criar intervalos onde o contorno de uma figura não seja representado. Deste modo, combinando a complexidade e intermitência dos exemplos anteriores, usando a primitiva `circle`, tem-se:" ] }, { "cell_type": "code", "execution_count": null, "id": "a6dea987", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", " \n", " \n", " \n", " \n", "" ] }, { "cell_type": "markdown", "id": "ffeda2b5", "metadata": {}, "source": [ "Expandindo ainda mais a complexidade da figura anterior e aumentando a quantidade dos objetos representados, pode-se obter, usando a primitiva `ellipse`:" ] }, { "cell_type": "code", "execution_count": null, "id": "96794e30", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "" ] }, { "cell_type": "markdown", "id": "8251b918", "metadata": {}, "source": [ "Usando a primitiva `path` para formar um objeto, pode-se desenhar sobre todo o caminho percorrido dentro de `d`. Para este exemplo, considera-se a regra de preenchimento `evenodd`, que alterna a cor do objeto de acordo com as intersecções entre suas componentes." ] }, { "cell_type": "code", "execution_count": null, "id": "78f21e3a", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", "" ] }, { "cell_type": "markdown", "id": "70fd86d6", "metadata": {}, "source": [ "Uma característica interessante de `path` é a possibilidade de combinar vários segmentos em uma única definição, isto é, um caminho pode ter vários pontos iniciais, denotados por `M`. Adicionando o modificador `z` ao final do caminho, indica-se que este deve ser fechado, isto é, que a última posição deve ser conectada à primeira, definida por `M`. Neste caso, igualmente, a intersecção dos objetos definidos entre `M` e `z`, no mesmo caminho `d` não é exibida." ] }, { "cell_type": "code", "execution_count": null, "id": "b6ffdd7b", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", "" ] }, { "cell_type": "markdown", "id": "ceefcc7e", "metadata": {}, "source": [ "A representação das curvas no SVG segue o algoritmo de Bézier, com uma posição final e uma direção na qual deve-se desviar, para aproximar o traço da forma de uma função não linear. Para expressar uma curva como uma função quadrada, usa-se o modificador `Q` dentro do caminho `d` de um `path`. Assim, tomando o mesmo valor para um conjunto de retas passando por um ponto até a posição de destino (definido por `L`, em vermelho) e uma curva indo na direção deste ponto (chamado de controle) e chegando ao destino (definida por `Q`, em azul), tem-se:" ] }, { "cell_type": "code", "execution_count": null, "id": "18613026", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", " \n", "" ] }, { "cell_type": "markdown", "id": "b3928121", "metadata": {}, "source": [ "Retornando ao exemplo da modificação da regra de preenchimento, pode-se modificá-lo de modo a incluir curvas quadráticas para obter um efeito similar. Deste modo, considerando os pontos que formam as retas como controle para as curvas e mantendo as origens e destinos dos segmentos, tem-se:" ] }, { "cell_type": "code", "execution_count": null, "id": "3965967f", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", " \n", "" ] }, { "cell_type": "markdown", "id": "5e4fe9eb", "metadata": {}, "source": [ "## Operações com SVG\n", "Até este momento, os exemplos apresentados apenas trabalharam com as primitivas básicas e seus atributos. Para criar imagens mais elaboradas, pode-se utilizar definições de operações, modelos e transformações que podem ser efetuadas sobre um objeto. A seguir serão apresentadas as transformações geométricas básicas implementadas no SVG com a operação `transform`.\n", "\n", "Inicialmente, considerando um exemplo anterior, pode-se fazer uma operação de translação usando o parâmetro `translate`, com os valores do deslocamento. Como os eixos do SVG crescem à direita e para baixo, uma translação com valores positivos moverá o objeto nesta direção:" ] }, { "cell_type": "code", "execution_count": null, "id": "d9d0434e", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", " \n", "" ] }, { "cell_type": "markdown", "id": "d6a30dd7", "metadata": {}, "source": [ "De forma similar, podemos realizar a escala usando a transformação `scale` para realizar a escala de uma figura. Caso esta seja uniforme, precisamos apenas de um valor:" ] }, { "cell_type": "code", "execution_count": null, "id": "9522d407", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", "" ] }, { "cell_type": "markdown", "id": "b2c90543", "metadata": {}, "source": [ "Caso deseje-se fazer a escala diferencial, deve-se informar os valores das coordenadas para cada eixo. Assim, para uma escala que mantenha o tamanho em `x` e dobre em `y`:" ] }, { "cell_type": "code", "execution_count": null, "id": "240cb9b5", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", " \n", "" ] }, { "cell_type": "markdown", "id": "b030ddb6", "metadata": {}, "source": [ "Para realizar uma rotação, pode-se fazer a operação `transform` com o atributo `rotate`, em graus, no sentido horário. Tomando um exemplo anterior, faz-se uma rotação de uma figura como:" ] }, { "cell_type": "code", "execution_count": null, "id": "fbe8858d", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", "" ] }, { "cell_type": "markdown", "id": "8eccb6e6", "metadata": {}, "source": [ "Neste caso, também, as operações de escala e rotação alteram as posições do objeto em relação à origem, o que pode ser considerado uma translação implícita. Desta forma, deve-se, igualmente, explicitar esta translação caso deseje-se realizar uma destas operações em relação a um ponto de referência distinto da origem. Assim, para realizar uma escala, por exemplo, faz-se uma translação do ponto de referência em relação à origem, a escala com este ponto sobre a origem, e a translação em relação à posição original do ponto. Novamente, as operações são realizadas da direita para a esquerda. Então, para escalar uma elipse (em preto), dobrando seu raio em $y$ (`ry`), em relação a seu centro (em vermelho):" ] }, { "cell_type": "code", "execution_count": null, "id": "d7735b0b", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", " \n", "" ] }, { "cell_type": "markdown", "id": "ccfa7bc8", "metadata": {}, "source": [ "Do mesmo modo, pode-se fazer uma rotação em relação a um ponto de referência compondo operações de translação e rotação. Assim, um exemplo anterior pode ser representado, fazendo uma rotação de $90°$ em torno do centro da figura, como:" ] }, { "cell_type": "code", "execution_count": null, "id": "cfa76596", "metadata": {}, "outputs": [], "source": [ "%%svg\n", "\n", " \n", "" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 5 }