ocsigen/tyxml

A semantic mistake in SVG `Transform `Rotate attribute

dborisog opened this issue · 6 comments

According to the documentation ( https://ocsigen.org/tyxml/4.3.0/api/Svg_types ), Transform attribute has `Rotate data type

`Rotate of Unit.angle * (float * float) option

with Unit.angle being

type angle = [ Deg | Grad | `Rad ] quantity

According to this logic, the following code should produce a vertical label

let yaxis_label ~size ~margin ~yaxis_text =
let yaxis_text = yaxis_text
in let xcoord = margin.left /. 3.
in let ycoord = size.height_p /. 2.
in Svg.D.[
text
~a:
[ a_x_list [xcoord, Some Px] ; a_y_list [ycoord, Some Px]
; a_text_anchor Middle ; a_dominant_baseline Middle
; a_fill (Color ("black", None)) ; a_font_size "16" ; a_transform [ Rotate ( (270., Some `Deg), Some (xcoord, ycoord) ) ] *)
]
[txt yaxis_text]
]

however, it produces a horizontal label. In Chrome Development tools it looks like

text class="caml_r" transform="rotate(270deg 30 200)" font-size="16" fill="black" dominant-baseline="middle" text-anchor="middle" y="200px" x="30px" data-eliom-id="EhX9pFflUqF9"
Birth rate
/text

while changing "270deg" to "270" (see below) makes the label vertical.

text class="caml_r" transform="rotate(270 30 200)" font-size="16" fill="black" dominant-baseline="middle" text-anchor="middle" y="200px" x="30px" data-eliom-id="EhX9pFflUqF9"
Birth rate
/text

According to my understanding of the following pages ( https://stackoverflow.com/questions/35584286/svg-transform-origin-not-working-in-chrome ) and ( https://css-tricks.com/transforms-on-svg-elements/ ), I changed the code moving the rotation above the text element as shown in the code below. It works.

let yaxis_label ~size ~margin ~yaxis_text =
let yaxis_text = yaxis_text
in let xcoord = margin.left /. 3.
in let ycoord = size.height_p /. 2.
in Svg.D.[
g
~a:
[ a_style (
Printf.sprintf
"transform: rotate(270deg); transform-origin: %spx %spx;"
(string_of_int (int_of_float xcoord) )
(string_of_int (int_of_float ycoord) )
)
]
[text
~a:
[ a_x_list [xcoord, Some Px] ; a_y_list [ycoord, Some Px]
; a_text_anchor Middle ; a_dominant_baseline Middle
; a_fill (Color ("black", None)) ; a_font_size "16" (* ; a_transform [ Rotate ( (270., Some `Deg), Some (xcoord, ycoord) ) ] *)
]
[txt yaxis_text] ]
]

However, it seems to me a semantic mistake was made, the current implementation uses (float, Some `Deg), which belongs to CSS attributes, while it should use float or a similar data type.

Horizontal label (wrong)
Screenshot from 2020-01-06 18-32-23

Vertical label (right)
Screenshot from 2020-01-06 18-29-50

Drup commented

Hmm, the specifications for SVG 2 and SVG 1.1 seem inconsistent. SVG2 refers to CSS transforms, which mandates use of the unit, while SVG 1.1 uses its own format, which doesn't have units. Seems like chrome doesn't use units.

How does it work with other browsers ?

Yes, the behavior is the same, both generated and removal of "deg" from "270deg" in DevTools, in

  • Chrome, 79.0.3945.88
  • Firefox, 71.0
  • Opera, 64.0.3417.92
  • (I do not have Yandex browser and Microsoft Edge on my Ubuntu 18.04 64 bit PC).
Drup commented

ok, so I suggest we get rid of all the units for rotate, keep only a float, and link to the msdn documentation. Are you willing to propose a patch ?

I think I need at least a half of a year's experience with Ocsigen's architecture and functional programming in OCaml in general prior to any pull requests to major projects.

The proposed solution might work, but I have limited experience with one case only. I used a modified Rotate example from ( https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform ) in ( https://www.w3schools.com/graphics/tryit.asp?filename=trysvg_text2 ) prior to opening this issue. I do not know how it would affect other Transform functions.

Most likely, SVG use is very limited at the moment, thus a narrow subset of Ocsigen users if any will be affected by the change. I think this change is needed, the use of extra [g] for style is not elegant.

UPD
An alternative to "float" is keeping the current Rotate Unit.angle data type while changing the output, e.g. "270" instead of "270deg". Yet again, I have not experimented with Grad and Rad options, nor I haven't followed browser development and specifics of SVG support and transition in major browsers.

Drup commented

After more research : When None is provided for units, nothing is printed, which is exactly what is expected in SVG 1.1. SVG 2, on the contrary, uses the same syntax as CSS, which mandates units.

In conclusion, I propose to not change the API at all: you should simply give None as units to ensure compatibility with current browsers.

My ability to judge is currently paralyzed by incomplete understanding of category theory. Category theory described mathematics from the perspective of changes of mathematical objects, which allows focusing on very important properties of mathematical structures.

In category theory, we have objects transformed into other objects by morphisms. This is very fitting for functional programming, where we transform one data type into another data type. The end-result of TyXML transformations is an HTML/SVG/XML document.

Category theory has multiple categories described, and it also has universal constructions, including initial object and terminal object. Bartosz Milewski (see pp. 52--55 https://github.com/hmemcpy/milewski-ctfp-pdf ), while providing examples on Haskell, states that Void stands for initial object and unit stands for _terminal object in Haskell. It might be very true for None and unit in OCaml. It might be important, as TyXML transformations fitting to the math of category theory could allow smooth construction of powerful (semi)polymorphic systems of functions.

For my incomplete understanding of category theory, I simply cannot comment on the rights/wrongs of the behavior you have described earlier. I am currently fixing my intellectual deficiency, with an intention to write a category-theory-driven data transformations in Ocsigen. I intend to comment on this issue as soon as I have sufficient theoretical knowledge and practical expertise.

Meanwhile, unless some words in this comment provided useful insights, the absence of changes on a thing that is already working (with PPX and functional workarounds) seems the right thing to do.