MathML или Latex – как мы реализовывали меняющийся оклад в расчете ЗП с помощью MathJax

в 5:14, , рубрики: javascript, latex, redmine, Блог компании Монастырёв и Ко, Веб-разработка, формулы, метки: , ,

Статья будет полезна web-разработчикам, которые задумались об отображении математических формул в браузере, ну и, наверное, другим IT-ам для общего развития.

У нас в компании уже давно внедрена система стимулирования сотрудников (KPI) на базе «Redmine», совмещающая функции расчета ЗП. Расскажу о ней в двух словах.

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

Все это может выглядеть примерно вот так:

image

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

Важная особенность, которую всегда нужно учитывать при внедрении KPI – это прозрачность! Сотрудник всегда должен знать, почему он получил именно такую ЗП, где он недоработал, почему показатель посчитался именно так.

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

Latex и MathML

На данный момент существуют два основных стандарта для отображения формул в браузере: Latex и MathML

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

Основное преимущество Latex в лаконичности записи. В остальном, по моему мнению, данный формат уступает MathML.

Например, запись формулы image в Latex выглядит вот так:

frac{(a+b )}{4} 

В MathML то же самое записалось бы гораздо более громоздко:

<math xmlns="http://www.w3.org/1998/Math/MathML">
    <mfrac>
       <mrow>
           <mi>a</mi>
           <mo>+</mo>
           <mi>b</mi>
       </mrow>    
       <mrow>
           <mn>4</mn>
       </mrow>
    </mfrac>
</math>

MathML – это ХМL в чистом виде, от сюда более простой парсинг для собственных нужд, в случае необходимости.

MathML показался более понятный и быстро осваиваемым форматом. Хотя это мнение индивидуальное и спорное.

MathML поддерживается большинством современных браузеров, кроме Chrome, который отказался от поддержки данного формата, сославшись на библиотеку MathJax. Это значит, что в Firefox, например, не нужно подключать дополнительные библиотеки, браузер распарсит и отобразит формулу указанную выше без помощи сторонних библиотек.

«MathML» позволяет дополнить формулу кучкой полезных дополнений. Нам, например, жизненно необходимо было получить всплывающую подсказку к определенным значениям формулы, что бы сотрудник видел, откуда появилось данное значение. Для этого в MathML есть тег maction:

<maction actiontype="tooltip">
     <mn mathsize="big">17 745 400</mn>
     <ms>Товарооборот</ms>
</maction>

В результате при наведении на значение, пользователю покажется всплывающая подсказка:

MathML или Latex – как мы реализовывали меняющийся оклад в расчете ЗП с помощью MathJax

Забавно, но самой понятной документаций по формату показалась документация Mozilla. Ее рекомендую, тем, кто будет ковыряться в данном формате: developer.mozilla.org/en-US/docs/Web/MathML/Element

MathJax

MathML хорош, но к сожалению, он не поддерживается всеми браузерами. Есть библиотека MathJax, которая позволяет парсить MathML в HTML, SVG и т.д. Она же, кстати, парсит отвергнутый мною Latex.

Библиотека неплохо справляется с парсингом формул, но имеет недостатки.

Самый большой, на мой взгляд, ее объем – 32.9 Mb в сжатом виде. Конечно не все будет отдаваться клиенту при отрисовке формулы, но сам по себе такой объем js-библиотеки, напрягает. Нам, например, нужно было раздавать ее внешним клиентам.

Изначально подключив всю библиотеку и реализовав задачу, я наугад выбросил из нее кучу папок. Методом «тыка», проверив, что ничего не сломалось. Размер сократился до 16 Mb. В основном выкидывал папки со шрифтами и лишними форматами вывода (например, SVG).

Грамотной документации по уменьшению объема библиотеки не нашел.

Библиотека достаточно тяжелая в освоении. Я долго ковырялся, в ней отключая ненужные фишки. Перечислю полезные конфигурационные настройки, которые оказались полезными:

Во-первых, у нас каждая формула открывается в отдельном окошке, которое грузится асинхронно (через AJAX). Следовательно, нет необходимости грузить библиотеку, когда пользователь не кликнул на ссылке. Примерно вот так это выглядит:

MathML или Latex – как мы реализовывали меняющийся оклад в расчете ЗП с помощью MathJax

Библиотека поддерживает динамическую загрузку (вот тут подробно про это написано docs.mathjax.org/en/latest/dynamic.html). Условно говоря, нужно динамически добавить объявление библиотеки в тег «head» и делать это только в том случае, если библиотека не подключалась ранее:

if(typeof $('body').data('mathjax_loaded') == 'undefined' )
          {
          //подключение библиотеки в тег head
          $('body').data('mathjax_loaded', 'true')
          }
else
          {
          MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
          }

При динамическом подключении библиотеки возникали проблем с рендерингом формулы. Формула рендерилась лишь в том случае, когда библиотек подгружалась в первые, при повторном клике и открытие окошка, формула не рендерилась. Помогла конструкция, которая принудительно запускала рендеринг формул на странице:

MathJax.Hub.Queue(["Typeset",MathJax.Hub]);

Рендеринг формулы происходит не мгновенно, поэтому пользователю лучше показать прелоадер, для этого есть конструкция:

mml2jax: {preview: [["img", {src: '/plugin_assets/kpi/images/loader.gif'}]]}

Иногда полезно увеличить масштаб:

HTML-CSS: { scale: 140 }

Если после рендеринга формулы необходимо запустить какой-то дополнительный javascript, то можно воспользоваться конструкцией:

MathJax.Hub.Queue(function () {
                alert('Test');
                });

Для отключения дополнительного меню, которое можно вызвать правым кликом мыши, и отключения зума формулы:

showMathMenu: false,
menuSettings: { zoom: false }

Две формулы, а не одна

Меня до последнего не покидала мысль парсинга математической формулы по которой собственно будет производиться расчет в MathML формат. То-есть в базе данных я хотел сохранять одну формулу вида:

31000.00 + (0.015*{"pattern": "imported_value", "id": "51"}/{"pattern": "imported_value", "id": "40"})

А mathml-конструкцию получать из исходной формулы:

<math xmlns="http://www.w3.org/1998/Math/MathML">
 <mrow>
   <mn>
     {"pattern": "result"}
   </mn>
   <mo>=</mo>
   <maction actiontype="tooltip">
      <mn>31 000</mn>
      <ms>Базовый оклад</ms>
   </maction>
   <mo>+</mo>
   <mfrac>
      <mrow>
         <mn>0.015</mn>
         <mo>×</mo>
         <maction actiontype="tooltip">
            <mn mathsize="big">{"pattern": "imported_value", "id": "51"}</mn>
            <ms>Товарооборот</ms>
         </maction>
      </mrow>    
      <mrow>
            <maction actiontype="tooltip">
                  <mn mathsize="big">{"pattern": "imported_value", "id": "40"}</mn>
                  <ms>Количество сотрудников</ms>
            </maction>
      </mrow>
   </mfrac>
</mrow>
</math>

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

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

Автор: tdvsdv

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js