Использование SVG для рисования набросков схем

  • Tutorial

Введение


Эта статья о том, как набросать простенькую схемку из десятка элементов, когда под рукой нет ни Altium'а, ни Orcad'a, ни даже Visio, а Draw.io внезапно сломался.

Это совсем не сложно: современные браузеры поддерживают язык разметки SVG, с помощью которого в обычном текстовом редакторе можно легко и быстро нарисовать небольшую схему типа:

Sketch of a low-power step-up converter


Далее я опишу процесс рисования и несколько тонкостей, полезных при этом. Ещё раз подчеркну, что большую схему намного проще нарисовать в специализированных приложениях, с автоматической трассировкой, мышкой, и прочим WYSIWYG'ом.
При чтении статьи советую представить корову в бомболюке, что для редактирования имеется nano через putty, а результат редактирования можно посмотреть на мобильном телефоне.

Создание наброска


Набросок обычно содержит элементы/блоки и связи между ними, и может быть создан в виде отдельного текстового файла с расширением .svg или напрямую встроен в код веб-страницы.

При рисовании схем удобно использовать определенный порядок действий:

  1. Создать (или скопировать) используемые элементы, например

    горизонтальный диод Шоттки
    <defs>
    ...
    <g width="30" height="10" id="schottky">
      <path
        style="fill:none;stroke:black;stroke-width:1"
        d="M0.5,5.5 h10 v-5 l10,5 l-10,5 v-5 m7,-4 v-2 h3 v12 h3 v-2 m-3,-4 h10"
      />
    </g>
    ...
    </defs>
    


    или

    вертикальный резистор
    <defs>
    ...
    <g width="30" height="10" id="resistor">
      <path
        transform="translate(1,0) rotate(90,15,5)"
        style="fill:none;stroke:black;stroke-width:1"
        d="M0.5,5.5 h3 l2,-3 l4,6 l4,-6 l4,6 l4,-6 l4,6 l2,-3 h3"
      />
    </g>
    ...
    </defs>
    


    Если элементы копируются из разных мест, уже на этом шаге стоит унифицировать их размеры. Вместо непосредственной правки координат для этого проще использовать преобразования (translate, rotate, scale и т.д.).

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

  2. Расставить элементы на схеме и подписать их. Для этого используется группа из тэга use для рисования элемента и одного или нескольких тэгов text для подписей.

    Пример
    ...
    <g transform="translate(115,45)"><use xlink:href="#resistor"/><text x="20" font-size="10">R1</text><text x="20" y="10" font-size="8">470k</text></g>
    ...
    


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

  3. Нарисовать соединения. Для этого удобно использовать тэг path, который позволяет легко чертить горизонтальные и вертикальные линии.

    Используются в основном следующие команды:

    • M10,5 — начать чертить с точки 10,5
    • h10 — горизонтальная линия, 10 пикселей вправо
    • m30,0 — перепрыгнуть на 30 пикселей вправо
    • v15 — вертикальная линия, 15 пикселей вниз
    • m0,30 — перепрыгнуть на 30 пикселей вниз
    • l-5,-10 — косая линия, 5 пикселей влево и 10 пикселей наверх

  4. Расставить точки над i соединения линий. Ничем не отличается от расстановки элементов, но лучше делать это после того, как все соединения уже нарисованы.

  5. Добавить красивостей по вкусу.

Код и картинка простой схемы

<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Определение элементов -->
<defs>
  <circle x="0.5" y="0.5" r="1.5" style="fill:blue;stroke:blue;" id="junction"/>
  <g width="30" height="10" id="connector">
    <path
	style="fill:none;stroke:black;stroke-width:1"
	d="M10.5,5.5 a3,3,0,0,1,-6,0 a3,3,0,0,1,6,0 h20"
    />
  </g>
  <g width="30" height="10" id="resistor">
    <path
	transform="translate(1,0) rotate(90,15,5)"
	style="fill:none;stroke:black;stroke-width:1"
	d="M0.5,5.5 h3 l2,-3 l4,6 l4,-6 l4,6 l4,-6 l4,6 l2,-3 h3"
    />
  </g>
</defs>
<!-- Расстановка элементов -->
<g transform="translate(5, 10)"><use xlink:href="#connector"/><text x="20" font-size="10">+</text> </g>
<g transform="translate(25,40)"><use xlink:href="#resistor"/> <text x="-5" y="10" font-size="10">R1</text></g>
<g transform="translate(5, 70)"><use xlink:href="#connector"/><text x="20" font-size="10">-</text> </g>
<!-- Связи -->
<path d="M35.5,15.5 h5 v15 m0,30 v15 h-5" stroke="red" fill="none"/>
<path d="M40.5,15.5 h25 v60 h-25" stroke="red" fill="none"/>
<!-- Соединения -->
<use xlink:href="#junction" transform="translate(40,15)"/>
<use xlink:href="#junction" transform="translate(40,75)"/>
<!-- Прочее -->
<text x="50" y="88" width="100" text-anchor="middle" font-family="cursive" font-size="10">Сопротивление</text>
<text x="50" y="98" width="100" text-anchor="middle" font-family="monospace" font-size="10">безвредно</text>
</svg>

Код схемы из начала статьи
<svg width="200" height="150" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<desc>Step-up DC-DC converter</desc>
<defs>
  <circle x="0.5" y="0.5" r="1.5" style="fill:blue;stroke:blue;" id="junction"/>
  <g width="30" height="10" id="connector">
    <path
	style="fill:none;stroke:black;stroke-width:1"
	d="M10.5,5.5 a3,3,0,0,1,-6,0 a3,3,0,0,1,6,0 h20"
    />
  </g>
  <g width="30" height="10" id="connector180">
    <path
	transform="translate(1,1) rotate(180,15,5)"
	style="fill:none;stroke:black;stroke-width:1"
	d="M10.5,5.5 a3,3,0,0,1,-6,0 a3,3,0,0,1,6,0 h20"
    />
  </g>
  <g width="30" height="10" id="resistor">
    <path
	transform="translate(1,0) rotate(90,15,5)"
	style="fill:none;stroke:black;stroke-width:1"
	d="M0.5,5.5 h3 l2,-3 l4,6 l4,-6 l4,6 l4,-6 l4,6 l2,-3 h3"
    />
  </g>
  <g width="30" height="10" id="capacitor">
    <path
	transform="translate(1,0) rotate(90,15,5)"
	style="fill:none;stroke:black;stroke-width:1"
	d="M0.5,5.5 h13 m0,-7 v14 m4,0 v-14 m0,7 h13"
    />
  </g>
  <g width="30" height="10" id="inductance">
    <path
	style="fill:none;stroke:black;stroke-width:1"
	d="M0.5,5.5 h0.7 a5,10,0,0,1,9.3,5 a5,10,0,0,1,10,0 a5,10,0,0,1,9.3,-5 h0.7"
    />
  </g>
  <g width="30" height="10" id="schottky">
    <path
	style="fill:none;stroke:black;stroke-width:1"
	d="M0.5,5.5 h10 v-5 l10,5 l-10,5 v-5 m7,-4 v-2 h3 v12 h3 v-2 m-3,-4 h10"
    />
  </g>
  <g width="40" height="40" id="stepup">
    <rect x="0.5" y="0.5" width="40" height="40" style="fill:none;stroke:black;stroke-width:1px;"/>
    <text x="2" y="10" font-size="8">VIN</text>
    <text x="2" y="37" font-size="8">GND</text>
    <text x="25" y="10" font-size="8">SW</text>
    <text x="27" y="37" font-size="8">FB</text>
  </g>
</defs>
  <g transform="translate(0,15)"><use xlink:href="#connector"/><text x="5" y="-5" font-size="10">+Vin</text></g>
  <g transform="translate(0,135)"><use xlink:href="#connector"/><text x="5" y="-5" font-size="10">Gnd</text></g>
  <g transform="translate(160,15)"><use xlink:href="#connector180"/><text y="-5" font-size="10">+Vout</text></g>
  <g transform="translate(160,135)"><use xlink:href="#connector180"/><text y="-5" font-size="10">Gnd</text></g>

  <g transform="translate(65,50)"><use xlink:href="#stepup"/><text y="55" font-size="10">SX1308</text></g>

  <g transform="translate(115,45)"><use xlink:href="#resistor"/><text x="20" font-size="10">R1</text></g>
  <g transform="translate(115,110)"><use xlink:href="#resistor"/><text x="20" font-size="10">R2</text></g>
  <g transform="translate(20,75)"><use xlink:href="#capacitor"/><text x="20" font-size="10">C1</text></g>
  <g transform="translate(140,75)"><use xlink:href="#capacitor"/><text x="20" font-size="10">C2</text></g>
  <g transform="translate(50,15)"><use xlink:href="#inductance"/><text x="10" y="-5" font-size="10">L1</text></g>
  <g transform="translate(95,15)"><use xlink:href="#schottky"/><text x="10" y="-5" font-size="10">D1</text></g>

  <path d="M30.5,20.5 h20 m30,0 h15 m30,0 h35" stroke="red" fill="none"/>
  <path d="M35.5,20.5 v45 m0,30 v45" stroke="red" fill="none"/>
  <path d="M130.5,20.5 v15 m0,30 v35 m0,30 v10" stroke="red" fill="none"/>
  <path d="M155.5,20.5 v45 m0,30 v45" stroke="red" fill="none"/>
  <path d="M45.5,20.5 v35 h20" stroke="red" fill="none"/>
  <path d="M50.5,140.5 v-55 h15" stroke="red" fill="none"/>
  <path d="M90.5,20.5 v20 h25 v15 h-10" stroke="red" fill="none"/>
  <path d="M105.5,85.5 h25" stroke="red" fill="none"/>
  <path d="M30.5,140.5 h130" stroke="red" fill="none"/>

  <use xlink:href="#junction" transform="translate(35,20)"/>
  <use xlink:href="#junction" transform="translate(45,20)"/>
  <use xlink:href="#junction" transform="translate(90,20)"/>
  <use xlink:href="#junction" transform="translate(130,20)"/>
  <use xlink:href="#junction" transform="translate(155,20)"/>
  <use xlink:href="#junction" transform="translate(130,85)"/>
  <use xlink:href="#junction" transform="translate(35,140)"/>
  <use xlink:href="#junction" transform="translate(50,140)"/>
  <use xlink:href="#junction" transform="translate(130,140)"/>
  <use xlink:href="#junction" transform="translate(155,140)"/>
</svg>


Тонкости рисования


В-общем, рисовать с помощью SVG достаточно просто. Ниже несколько не очень очевидных, но способных сэкономить немного времени деталей.

Почему размываются линии



SVG использует суб-пиксельную точность при рисовании линий. Поэтому для линий толщиной в нечетное число пикселей координаты начала и конца должны располагаться в середине пикселя. Если для рисования использовались только относительные координаты, то можно просто сдвинуть координаты начальной точки на 0.5,0.5. Универсальное решение — translate(0.5,0.5).

Круги или дуги


Круг можно нарисовать тэгом circle или командой a (от Arc — дуга) тэга path. Если есть выбор, используйте circle.

Единственное оправдание рисованию круга с помощью дуг — если хочется нарисовать элемент, содержащий круги, одним единственным тэгом path. Для этого потребуется две последовательные дуги: ... a3,3,0,0,1,-6,0 a3,3,0,0,1,6,0 ..., где 3 — радиус круга, а 6 — его диаметр.

Использование элементов из других файлов


Очень просто — вместо <use xlink:href="#connector"/> используется <use xlink:href="library.svg#connector"/>.

Отдельный файл или встроенный в HTML код


Отдельный файл намного удобнее, но

  • если .svg содержит ссылки на другие .svg, то его нужно включать в HTML тэгом object

    <object data="image.svg" type="image/svg+xml"></object>

  • если .svg не содержит ссылок на другие .svg, то тэг image работает не хуже тэга object
    <img src="image.svg"/>
    <object data="image.svg" type="image/svg+xml"></object>
    

  • Элементы встроенного изображения проще и быстрее настраиваются через CSS

Где брать изображения элементов


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

Как из .svg получить .pdf, .png, .jpg, и т.д.


Самое простое — открыть .svg в Edge и сохранить изображение как .png. Если браузер не позволяет такого, то можно распечатать .svg на PDF принтер или использовать PrintScreen.

Также есть куча онлайн сервисов, например CloudConvert. К сожалению, не все сервисы правильно обрабатывают прозрачность и/или нестандартные шрифты.
Поделиться публикацией

Комментарии 17

    +3

    Вы можете использовать оригинальные SVG изображения в статье опубликовав их на Github Gist.

      0
      Там используется rawgit, который является сторонним сервисом к Гитхабу. Это не так надёжно, как непосредственно на Github Pages (github.io).
        0

        Можно и Github Pages и ipfs.io использовать. Но это уже квест по сложнее.

          0
          Спасибо за подсказку, квест с Github Pages оказался несложным.
      +2
      Пример graphviz нам показывает, что иногда можно вполне просто нарисовать сложные вещи в виде текста.

      Главное — придумать подходящий язык, и свалить на программу реально сложные вещи типа автоматического размещения компонентов на листе, трассировку соединений и пр. Но вот это, к сожалению, сделать уже не очень просто.
        0

        Да хотя бы расширить пункт 3, включив туда переработанный пункт 4.


        "Нарисовать соединения. Для этого удобно использовать тэг path, который позволяет легко чертить горизонтальные и вертикальные линии."
        … тут список команд из алгола…
        … вот тут что-то из п.4, например, как-то так:
        "Расставить точки соединения линий. Ничем не отличается от расстановки элементов, но лучше знать заранее возможные точки соединений." Но точки соединение, читай In/Out, никак не выделены, пусть это будет технический тэг. И команда построения линии не по координатам, а "от точки A1 до точки B1", где [A;B] — объект, [0..9] — точки соединения. Как-то это стандартизировать… а вдруг уже, просто я не в курсе. А там и до трассеров как-нибудь…
        Уйма библиотек в Egale, Fritzin, Proteus и т.д. и все несовместимы, а те вон из гугла — бесполезны чуть менее, чем совсем.
        SVG(он же XML, так?) вполне, как мне кажется, подошёл бы для всего этого. Не обязательно выдумывать какой-то новый язык

          0

          SVG для графики. Пихать в него, например, информацию о сетях (а она необходима для нормальной трассировки, чтобы понимать какие компоненты реально соединены, а где просто линии пересеклись на схеме), — это значит сделать кастомный формат поверх svg, т. к. svg-библиотеки уже не смогут с этим работать: например, разделение одного path на два потребует каких-то дополнительных телодвижений, сообщающих о том, что они всё ещё относятся к одной сети.


          Я видел использование подобного подхода в SCADA (TAC Vista): TGML, описывающий мнемосхему, является надмножеством svg, который содержит информацию об обработке событий и биндингах к сигналам реального мира.

            0

            Но есть же в этом svgml вот это вот
            https://www.w3.org/TR/SVG2/struct.html#MetadataElement

              0

              А каким образом metadata запретит стороннему редактору разбить/объединить какие-нибудь path? Оно в этом месте в любом случае оказывается хрупким.

                0

                Мммм, факт… знал бы ответ....

          0
          К счастью, всё это уже сделано, и многократно.
          Правда иногда использовать такого монстра проектирования для рисования простой картинки — всё равно, что стрелять из пушки по воробьям.
          Я же описываю противоположную крайность. Метафорически, когда дырка нужна, а сверла нет, то и гвоздиком проковырять можно.
          0

          По работе приходилось сталкиваться с svg, но всегда через редакторы. Было желание ковырнуть код, но всегда останавливала лень и "ну не серьезно это".


          А так остается вставить картинку про "как рисовать сову на svg в блокноте"

            +1

            "Как натянуть SVG сову на SVG глобус" (ASCII-видео туториал) :)


            P.S. а мне нравятся редакторы схем, где можно таскать элементы вместе с присоединёнными к ним "резиновыми" связями.
            Простейшие схемы можно не рисовать, а из типовых даташитных и википедийных выдернуть, если допустим назначение резистора нужно кому-то объяснить по его номеру.

              0

              Это да, тема. Особенно если оно ещё и маршрут перерисовывает натлету

            0

            XML вообще плохой формат для ручной обработки и написания.
            И поэтому вручную с помощью него рисовать — самое последнее дело.


            Если хочется рисовать графику в текстовом редакторе, то есть более подходящие форматы:
            PostSctipt, MetaPost


            Которые можно конвертируются в форматы, которые понимают браузеры.


            А если надо серьезно рисовать в SVG, то лучше всего Inkscape.

              0
              Про самое последнее дело — совершенно согласен. Но если вдруг припрёт, то текстовый редактор найти проще, чем конвертер Постскрипта. Кроме того, документация по SVG кажется мне более полной и понятной.
                0

                А если кроме текстового редактора, завалялся еще и язык python, то можно использовать библиотеку обертку для рисования, например: svgwrite :)
                Думаю и для других языков есть нечто подобное.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое