Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • master
  • vS5_2020-2021
2 results

Target

Select target project
  • rvuillem/INF-TC2
  • teixeira/INF-TC2
  • mdemeill/INF-TC2
  • epelous/INF-TC2
  • rgayet/INF-TC2
  • gparamuc/INF-TC2
  • pdewilde/INF-TC2
  • bnegre/INF-TC2
  • elichiya/INF-TC2
  • amacgreg/INF-TC2
  • yjaid/INF-TC2
  • lcholley/INF-TC2
  • hwei/INF-TC2
  • lmelodef/INF-TC2
  • mnauche/INF-TC2
  • nbernier/INF-TC2
  • gbouvier/INF-TC2
  • lalbin/INF-TC2
  • jdelaffo/INF-TC2
  • ndjaoud/INF-TC2
20 results
Select Git revision
  • master
1 result
Show changes
Showing
with 686 additions and 71 deletions
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="701px" height="286px" viewBox="-0.5 -0.5 701 286" content="&lt;mxfile host=&quot;app.diagrams.net&quot; modified=&quot;2020-09-02T12:43:26.686Z&quot; agent=&quot;5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36&quot; etag=&quot;WZoK8idbTfEnltmXA0ep&quot; version=&quot;13.6.6&quot; type=&quot;device&quot;&gt;&lt;diagram id=&quot;ZFG0j7v5SNXi1jvCDSdx&quot; name=&quot;Page-1&quot;&gt;7Vtdb+I4FP01PFKRT+ARaJkZCVbdstqR5mVlEpNYNTFyTIH++r3BNiFxYJh2A+1sJNTGN7Zzfc/x9YkNLWe03H7haBVPWYhpy+6E25Zz37Ltbs+Hv5lhJw1u15WGiJNQmqzcMCOvWBk7yromIU4LFQVjVJBV0RiwJMGBKNgQ52xTrLZgtPjUFYqwYZgFiJrW7yQUsbT27G5u/4pJFOsnW35f3lkiXVmNJI1RyDZHJueh5Yw4Y0JeLbcjTLPY6bjIduMTdw+OcZyISxrYEZ+K7e7v/vTHcPb1n6dgMA3ani27eUF0rUb8BGFESQQBkG6LnY5FuiFLihIoDRcsETN1x4IyoiRK4DoAZzAHwwvmgkAYB+qGYCuwBjGh4QTt2DpzORUoeNalYcw4eYVuEVV9wm0uFCNsv1BjlrUEcwesHKdQ51HHwSqZpmhbqDhBqVCGgFGKVimZH4axRDwiyZAJwZaqEuKB8sHyMqcwRBwJxkeMMhjofcL2AdGxGRNKy7c08Fl3EUVpqq51mwlJtAPpMxZBrB1m6yTEoSqtMCdLDNGdrVBAkkiZF0cPbNnOYrGwg2AfPc6e8dGd0J/7XhZGkziKSxlmeHtkUkT6ghk8mO+gip7WitNqUju6vMmnyIH3cWF6KL4hNS2jQ9c5c+FCkfdXiOwYRG5DEfoYwD8YKQFalgkNoxVGqBRqCxNITXKKF+IkxVOJzmRf597NLU8qDJmJQdsF3ZMiJmGIkz3aAgkkubjHm5FE7MPkDeED0Rx17ryWB46PoGzlZfhk1bkYsQTGgsgeVwxE3+CM7BWIn08GP+eBAt72L8Nd13sP7N/+/DZ55ZvB9yd3EwzaIWLjP9pm+spQjxvU60Rdt7gG6tUuewbsBsqUyOS7R1kv3dabIF4CWBTnmP6VQX7ftgzcHRN3pwJjiuaYPrKUCMKy/rmsW8L+ZvB2e5fB26sLXd9Ed2S3Bh0D42YmvxPq3hXzd7XLXQPrB0pB3Dfqs1Gfp9SnY308+dmrFCJ8K5VIyNbziheqJn9VJ4PPoz9NIbKHfdfAXifsNxegfqcRoPXBe0UBWjmpG/15LaRvrj99cwOh5Ywb7dlozxPa0/PeqD17tVHY3Pm8u7trctX5XHWY+B9Sa1a73Ox61QjvpZqyLtFhVUjKRnXUArV/YcqubyabuwUGyjgJB9n5NZTmlGVr/jAWS60QcBhhrUIghDGLWILoQ24tLaHQm17LfVnMlmu9GheyhX1uoUzZmgf4nAZUIwEFE+FzsLkquNlIzoJ2hJJXAZK2cUyRIC+44G4VcuoJjxlRj/aUnNK63u8Uu5AjV63so2P2cke9UkduqSMZGaMjABvtjqqpiXTSYatfeo7XOeuXa5+tDxfSg5zVBwzeQfT+70v07uckut0p8aD3RqK75Rmjsf0J0f8rbnUrFszfhFt6Sfts3DIocW1u/WoS9ctvcx8xibrmftSY8WVzQtbsUpzapSifkB2S/u1OyFzzpLdlD6XLzZd1Ln3ZOeSCz7Nt4VWt00PpYAN8fcDf/JDMveAtt9mveiu8t/6WVtcy0f1/71cV5cBpVXEsIQwNVRtfajxqg2L+MwapevPfgjgP/wI=&lt;/diagram&gt;&lt;/mxfile&gt;"><defs><clipPath id="mx-clip-4-201-212-26-0"><rect x="4" y="201" width="212" height="26"/></clipPath><clipPath id="mx-clip-4-227-212-26-0"><rect x="4" y="227" width="212" height="26"/></clipPath><clipPath id="mx-clip-4-261-212-26-0"><rect x="4" y="261" width="212" height="26"/></clipPath><clipPath id="mx-clip-244-201-212-26-0"><rect x="244" y="201" width="212" height="26"/></clipPath><clipPath id="mx-clip-244-227-212-26-0"><rect x="244" y="227" width="212" height="26"/></clipPath><clipPath id="mx-clip-244-261-212-26-0"><rect x="244" y="261" width="212" height="26"/></clipPath><clipPath id="mx-clip-484-201-212-26-0"><rect x="484" y="201" width="212" height="26"/></clipPath><clipPath id="mx-clip-484-235-212-26-0"><rect x="484" y="235" width="212" height="26"/></clipPath><clipPath id="mx-clip-244-31-212-26-0"><rect x="244" y="31" width="212" height="26"/></clipPath><clipPath id="mx-clip-244-57-212-26-0"><rect x="244" y="57" width="212" height="26"/></clipPath><clipPath id="mx-clip-244-91-212-26-0"><rect x="244" y="91" width="212" height="26"/></clipPath></defs><g><path d="M 0 196 L 0 170 L 220 170 L 220 196" fill="#fff2cc" stroke="#d6b656" stroke-miterlimit="10" pointer-events="all"/><path d="M 0 196 L 0 282 L 220 282 L 220 196" fill="none" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><path d="M 0 196 L 220 196" fill="none" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"><text x="109.5" y="187.5">Rectangle</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-4-201-212-26-0)" font-size="12px"><text x="5.5" y="213.5">- l: entier</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-4-227-212-26-0)" font-size="12px"><text x="5.5" y="239.5">- h: entier</text></g><path d="M 0 252 L 220 252" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-4-261-212-26-0)" font-size="12px"/><path d="M 240 196 L 240 170 L 460 170 L 460 196" fill="#fff2cc" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><path d="M 240 196 L 240 282 L 460 282 L 460 196" fill="none" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><path d="M 240 196 L 460 196" fill="none" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"><text x="349.5" y="187.5">Ellipse</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-201-212-26-0)" font-size="12px"><text x="245.5" y="213.5">- rx: double</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-227-212-26-0)" font-size="12px"><text x="245.5" y="239.5">- ry: double</text></g><path d="M 240 252 L 460 252" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-261-212-26-0)" font-size="12px"/><path d="M 480 196 L 480 170 L 700 170 L 700 196" fill="#fff2cc" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><path d="M 480 196 L 480 256 L 700 256 L 700 196" fill="none" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><path d="M 480 196 L 700 196" fill="none" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"><text x="589.5" y="187.5">?</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-484-201-212-26-0)" font-size="12px"><text x="485.5" y="213.5">...</text></g><path d="M 480 226 L 700 226" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-484-235-212-26-0)" font-size="12px"/><path d="M 120 170 L 120 150 L 350 150 L 350 132.24" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><path d="M 350 114.24 L 359 132.24 L 341 132.24 Z" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><path d="M 350 170 L 350 132.24" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><path d="M 350 114.24 L 359 132.24 L 341 132.24 Z" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><path d="M 580 170 L 580 150 L 350 150 L 350 132.24" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><path d="M 350 114.24 L 359 132.24 L 341 132.24 Z" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><path d="M 240 26 L 240 0 L 460 0 L 460 26" fill="#fff2cc" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><path d="M 240 26 L 240 112 L 460 112 L 460 26" fill="none" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><path d="M 240 26 L 460 26" fill="none" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"><text x="349.5" y="17.5">Forme</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-31-212-26-0)" font-size="12px"><text x="245.5" y="43.5">+ x: entier</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-57-212-26-0)" font-size="12px"><text x="245.5" y="69.5">+ y: entier</text></g><path d="M 240 82 L 460 82" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-91-212-26-0)" font-size="12px"/></g></svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="701px" height="482px" viewBox="-0.5 -0.5 701 482" content="&lt;mxfile host=&quot;app.diagrams.net&quot; modified=&quot;2020-09-02T12:43:54.091Z&quot; agent=&quot;5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36&quot; etag=&quot;lFbpCapjlEMSZ9oZFN9N&quot; version=&quot;13.6.6&quot; type=&quot;device&quot;&gt;&lt;diagram id=&quot;ZFG0j7v5SNXi1jvCDSdx&quot; name=&quot;Page-1&quot;&gt;7Vxdb6M4FP01kboPrQKEfDwmaTszq3Y124x21HmpHHDAGgdHxpkk/fV7ATsfGFLahaSdtVR14osx9j3H1/eeMG054/n6E0eL8J75mLbstr9uOdct2+71u/A7MWwyQ6fXyQwBJ35msnaGCXnG0tiW1iXxcXzQUTBGBVkcGj0WRdgTBzbEOVsddpsxevjUBQqwZph4iOrW78QXYWbt272d/TMmQaiebHUH2ZU5Up3lSuIQ+Wy1Z3JuWs6YMyayT/P1GNPEd8ov2X23JVe3E+M4ElVusAN+L9abfwb3P0aTz08P3vDeu3TtbJhfiC7lih/AjSgKwAHZtMVG+SJekTlFEbRGMxaJibxiQRtREkTw2YPJYA6GX5gLAm4cyguCLcDqhYT6d2jDlsmUY4G8n6o1ChknzzAsonJMuMyFZITdPegxSe4EcxusHMfQ56vyg5Uz3aP1Qcc7FAtp8BilaBGT6XYZc8QDEo2YEGwuOyHuyTlYbjIpDB5HgvExowwWeh2x1CHKN7eE0vwlBXwyXEBRHMvP6p47EqkJxD+x8EI1YbaMfOzL1gJzMsfg3ckCeSQKpHm298CW7cxmM9vzUu9x9hPvXfG7066buFEnjuRSghle75kkkT5hBg/mG+iitrXktNzUjmqvdltky/twb3vYPRkOkNyWwXboHXPhgyTva4jsaES+hCaMMYR/YKUEaJknNKxWaK6SqM10IBXJKZ6JUorHGTp3aZ/rzs7yIN2QmBjcO6MpKULi+zhK0RZIoIyLKd6MRCJ1kzuCH/DmuH3ltlyY+Bja1q4NP0l3LsYsgrUgkuKKgegrnJC9APHjweBlHkjg7W5F3GuA/cvfX+6e+Wr4/aGz8oaXPmK3f13q4StBPTSoN4m6uuMUqBdP2dVg11CmJAu+Kcrq6LbeBPEcwKJ4h+m3BPLrS0vD3dFxdwowpmiK6VcWE0FYMj7P+uawPxu8vX41ePtNodstQHeU5XjgiaUn8JJfrA/3+Dibf84EIzlW6nK990GQ+MNEibpp1K94Njidhs4Gq4RGaDYjXogvDOa1Y27ZVRPBOo6Gif8jfJz2rec/h5vHx/V18GNYCnqAxZNP5gB6uutfigeGGrVTo3PCXLGQGnqumFEjltQwZ8TpSdE7YSpZeEjoZeM21wDMI/GUuvHFbCMLK9t0Y8oYbY2d1nAA0BjW1M2aQcUM1bEbYk2nhDUcQyDBUQx5/RMsPCNPfLFul2emG/1ae20V0M0ysahZLcOpmLt0Gitrexqtbigli9hIsUaKLZNiHfVNyeu12MYK+L7G40SV4/IQ9dlyWvDtgglgxcHg44ixuiqXwr4xsDcJ+9nV2G67IBsyamxN8J5QjS3c1A2IsfmDYKxFCZPe1s6jU8qxxWHC6LEnB/38emyZvpLTY18OCYYctZPj7IpsmYyiFFlzUJyDFmfXZPXy0Wiy7541lTXZpr7uHZSwxmiyH5dVWwX/bJps19Zp5dxqSBs91uix2VXXfaMeaw0a47CehF9dXZlodTxabXf+uxRgi6ds3otsEN6qQmtTSpz6oue4FAc72yQitWPfPaGOUgy+Uc9ODnpVybQ50AuST1MJv3PWWAq1Fyvhxg6KMtHVlMIfmFZuxSOouVK4SJfLwYwjf5j8r1toTSlLCtFRKOaqbMV+gFVpDD4MWcAiRG921lxdB6OpArObNZMaUl48zGDtY9VbzJbcw0eWplI7KKsDfAw35dxkJUdR20PJLQBJ2TimSJBf+GC6RcjJJ3xNmLorNh0nV2wO2odDZCuXd+3w1wfq5wbq5AbKPKMNBGCjzV43uZNKJ2wNcs9x20fn1bGP9ocP2Qx2rN5i8B+IXiQl/iZE731MotvtHA/6byR6J79jFLYvEL0ubik16DfkljrTPhq3NEqcmluvDaLdvMT4HoNoR39x6JbxuXmV2UjnZdJ5/lVmq7J0XkcpVcxi/ZX8rJTKldym2qkS8D+QlO4WndOjvLBigK8b+LO/zdypUOWa71DeCu+5/7ZEr0xGf9XrzP9feesweSjPQfYTDi3jaoxdpxTsi6dc9hKSjxcUeXgOK77wC+jlG341G3gqq/Jv4AY0d3/GL6ufdn8L0bn5Fw==&lt;/diagram&gt;&lt;/mxfile&gt;"><defs><clipPath id="mx-clip-4-231-212-26-0"><rect x="4" y="231" width="212" height="26"/></clipPath><clipPath id="mx-clip-4-257-212-26-0"><rect x="4" y="257" width="212" height="26"/></clipPath><clipPath id="mx-clip-4-291-212-34-0"><rect x="4" y="291" width="212" height="34"/></clipPath><clipPath id="mx-clip-4-325-212-26-0"><rect x="4" y="325" width="212" height="26"/></clipPath><clipPath id="mx-clip-4-351-212-26-0"><rect x="4" y="351" width="212" height="26"/></clipPath><clipPath id="mx-clip-4-377-212-26-0"><rect x="4" y="377" width="212" height="26"/></clipPath><clipPath id="mx-clip-4-403-212-32-0"><rect x="4" y="403" width="212" height="32"/></clipPath><clipPath id="mx-clip-4-435-212-46-0"><rect x="4" y="435" width="212" height="46"/></clipPath><clipPath id="mx-clip-244-231-212-26-0"><rect x="244" y="231" width="212" height="26"/></clipPath><clipPath id="mx-clip-244-257-212-26-0"><rect x="244" y="257" width="212" height="26"/></clipPath><clipPath id="mx-clip-244-291-212-34-0"><rect x="244" y="291" width="212" height="34"/></clipPath><clipPath id="mx-clip-244-325-212-26-0"><rect x="244" y="325" width="212" height="26"/></clipPath><clipPath id="mx-clip-244-351-212-26-0"><rect x="244" y="351" width="212" height="26"/></clipPath><clipPath id="mx-clip-244-377-212-26-0"><rect x="244" y="377" width="212" height="26"/></clipPath><clipPath id="mx-clip-244-403-212-34-0"><rect x="244" y="403" width="212" height="34"/></clipPath><clipPath id="mx-clip-244-437-212-46-0"><rect x="244" y="437" width="212" height="46"/></clipPath><clipPath id="mx-clip-484-231-212-26-0"><rect x="484" y="231" width="212" height="26"/></clipPath><clipPath id="mx-clip-484-265-212-26-0"><rect x="484" y="265" width="212" height="26"/></clipPath><clipPath id="mx-clip-484-291-212-26-0"><rect x="484" y="291" width="212" height="26"/></clipPath><clipPath id="mx-clip-484-317-212-38-0"><rect x="484" y="317" width="212" height="38"/></clipPath><clipPath id="mx-clip-484-355-212-46-0"><rect x="484" y="355" width="212" height="46"/></clipPath><clipPath id="mx-clip-244-31-212-26-0"><rect x="244" y="31" width="212" height="26"/></clipPath><clipPath id="mx-clip-244-57-212-26-0"><rect x="244" y="57" width="212" height="26"/></clipPath><clipPath id="mx-clip-244-91-212-26-0"><rect x="244" y="91" width="212" height="26"/></clipPath><clipPath id="mx-clip-244-117-212-26-0"><rect x="244" y="117" width="212" height="26"/></clipPath></defs><g><path d="M 0 226 L 0 200 L 220 200 L 220 226" fill="#fff2cc" stroke="#d6b656" stroke-miterlimit="10" pointer-events="all"/><path d="M 0 226 L 0 476 L 220 476 L 220 226" fill="none" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><path d="M 0 226 L 220 226" fill="none" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"><text x="109.5" y="217.5">Rectangle</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-4-231-212-26-0)" font-size="12px"><text x="5.5" y="243.5">- l: entier</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-4-257-212-26-0)" font-size="12px"><text x="5.5" y="269.5">- h: entier</text></g><path d="M 0 282 L 220 282" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-4-291-212-34-0)" font-size="12px"><text x="5.5" y="303.5">+ constructeur(x: entier, y: entier,</text><text x="5.5" y="317.5">l: entier, h: entier)</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-4-325-212-26-0)" font-size="12px"><text x="5.5" y="337.5">+ affiche()</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-4-351-212-26-0)" font-size="12px"><text x="5.5" y="363.5">+ get_dim(): l: entier, h: entier</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-4-377-212-26-0)" font-size="12px"><text x="5.5" y="389.5">+ set_dim(l: entier, h: entier)</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-4-403-212-32-0)" font-size="12px"><text x="5.5" y="415.5">+ contient_point(x: entier, y: entier):</text><text x="5.5" y="429.5">booléen</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-4-435-212-46-0)" font-size="12px"><text x="5.5" y="447.5">+ redimension_par_points(x0: entier,</text><text x="5.5" y="461.5">y0: entier, x1: entier, y1: entier)</text></g><path d="M 240 226 L 240 200 L 460 200 L 460 226" fill="#fff2cc" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><path d="M 240 226 L 240 478 L 460 478 L 460 226" fill="none" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><path d="M 240 226 L 460 226" fill="none" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"><text x="349.5" y="217.5">Ellipse</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-231-212-26-0)" font-size="12px"><text x="245.5" y="243.5">- rx: double</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-257-212-26-0)" font-size="12px"><text x="245.5" y="269.5">- ry: double</text></g><path d="M 240 282 L 460 282" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-291-212-34-0)" font-size="12px"><text x="245.5" y="303.5">+ constructeur(x: entier, y: entier,</text><text x="245.5" y="317.5">rx: double, ry: double)</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-325-212-26-0)" font-size="12px"><text x="245.5" y="337.5">+ affiche()</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-351-212-26-0)" font-size="12px"><text x="245.5" y="363.5">+ get_dim(): rx: double, ry: double</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-377-212-26-0)" font-size="12px"><text x="245.5" y="389.5">+ set_dim(rx: double, ry: double)</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-403-212-34-0)" font-size="12px"><text x="245.5" y="415.5">+ contient_point(x: entier, y: entier):</text><text x="245.5" y="429.5">booléen</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-437-212-46-0)" font-size="12px"><text x="245.5" y="449.5">+ redimension_par_points(x0: entier,</text><text x="245.5" y="463.5">y0: entier, x1: entier, y1: entier)</text></g><path d="M 480 226 L 480 200 L 700 200 L 700 226" fill="#fff2cc" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><path d="M 480 226 L 480 396 L 700 396 L 700 226" fill="none" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><path d="M 480 226 L 700 226" fill="none" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"><text x="589.5" y="217.5">?</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-484-231-212-26-0)" font-size="12px"><text x="485.5" y="243.5">...</text></g><path d="M 480 256 L 700 256" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-484-265-212-26-0)" font-size="12px"><text x="485.5" y="277.5">+ constructeur(...)</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-484-291-212-26-0)" font-size="12px"><text x="485.5" y="303.5">+ affiche()</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-484-317-212-38-0)" font-size="12px"><text x="485.5" y="329.5">+ contient_point(x: entier, y: entier):</text><text x="485.5" y="343.5">booléen</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-484-355-212-46-0)" font-size="12px"><text x="485.5" y="367.5">+ redimension_par_points(x0: entier,</text><text x="485.5" y="381.5">y0: entier, x1: entier, y1: entier)</text></g><path d="M 120 200 L 120 180 L 350 180 L 350 158.24" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><path d="M 350 140.24 L 359 158.24 L 341 158.24 Z" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><path d="M 350 200 L 350 158.24" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><path d="M 350 140.24 L 359 158.24 L 341 158.24 Z" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><path d="M 580 200 L 580 180 L 350 180 L 350 158.24" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><path d="M 350 140.24 L 359 158.24 L 341 158.24 Z" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><path d="M 240 26 L 240 0 L 460 0 L 460 26" fill="#fff2cc" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><path d="M 240 26 L 240 138 L 460 138 L 460 26" fill="none" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><path d="M 240 26 L 460 26" fill="none" stroke="#d6b656" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"><text x="349.5" y="17.5">Forme</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-31-212-26-0)" font-size="12px"><text x="245.5" y="43.5">+ x: entier</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-57-212-26-0)" font-size="12px"><text x="245.5" y="69.5">+ y: entier</text></g><path d="M 240 82 L 460 82" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-91-212-26-0)" font-size="12px"><text x="245.5" y="103.5">+ constructeur(x: entier, y: entier)</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-244-117-212-26-0)" font-size="12px"><text x="245.5" y="129.5">+ deplacement(dx: entier, dy: entier)</text></g></g></svg>
\ No newline at end of file
# <center>TD2 : Modélisation de formes géométriques</center>
**Sommaire**
Nous allons aborder dans ce TD le concept d'héritage de la programmation objet, et l'utilisation de tests unitaires pour guider le développement logiciel et améliorer sa qualité.
[[_TOC_]]
Le but de ce TD est de concevoir un module pour manipuler des formes géométriques avec Python. Ce module sera utilisé dans les TDs suivants, donc les tests seront essentiels pour limiter les éventuels bugs. Vous commencerez par définir les classes et leurs attributs, puis écrirez les tests unitaires de votre module, et terminerez par l'implémentation des méthodes.
# BE #2 : Modélisation de formes géométriques
Le but de ce BE est d'illustrer le concept d'héritage de la programmation objet, en concevant un module pour manipuler des formes géométriques avec Python. Vous commencerez par définir les classes et leurs attributs, puis implémenterez les méthodes, et les validerez avec des tests.
Pour compléter cet énoncé, la dernière section propose de réfléchir à une schéma UML d'une application décrite par son cahier des charges.
# Modélisation avec UML (1h)
---
## Modélisation avec UML (1h30)
Les formes géométriques sont représentées par des classes, et l'héritage sera utilisé pour factoriser les propriétés communes. Nous nous limitons à un repère à deux dimensions orthonormé, avec les axes croissant vers la droite et le bas. Les coordonnées dans ce repère sont des entiers relatifs (c'est-à-dire possiblement négatifs). Dans cet espace, nous choisissons de représenter les formes suivantes :
* Les rectangles caractérisés par leur origine (`x`, `y`) et leurs dimensions (`l`, `h`).
* Les ellipses caractérisées par leur origine (`x`, `y`) et leurs rayons aux axes (`rx`, `ry`).
* Un type de forme de votre choix (ex. triangle, polygone, étoile, ...), qui possède au moins une origine (`x`, `y`).
* Les cercles caractérisés par leur origine (`x`, `y`) et leur rayon.
<center><img src="figures/formes.svg" style="width:50%"/></center>
<center><img src="figures/formes.svg" style="width:70%"/></center>
__Exercice 1 -__ Représentez les 3 classes dans un diagramme de classes UML (_voir https://app.diagrams.net pour dessiner en ligne, avec l'onglet UML sur la gauche_). Il est recommandé de commencer les noms des classes par une majuscule et les attributs par une minuscule. Les attributs devraient-ils être publics ou privés ?
Les attributs `x` et `y` étant partagés par les trois classes, on introduit l'héritage pour les regrouper. Toutes les formes géométriques hériteront d'une même classe __Forme__. L'intérêt de cette classe est double :
* Du point de vue des développeurs du module, les méthodes dont le code est identique entre formes (ex. translation) sont fusionnées dans __Forme__, réduisant la quantité de code à (et donc la multiplication des erreurs possibles).
* Du point de vue des utilisateurs du module, on peut écrire du code qui manipule des rectangles et des ellipses (ex. système de collisions de formes) sans avoir à écrire du code séparément pour les rectangles et les ellipses. Cet aspect sera illustré dans un prochain TD.
__Exercice 1 -__ Représentez les 3 classes dans un diagramme de classes UML (_voir [diagrams.net](https://app.diagrams.net) pour dessiner en ligne, avec l'onglet UML sur la gauche de l'interface_). Il est recommandé de commencer les noms des classes par une majuscule et les attributs par une minuscule. Durant tout ce BE on considérera uniquement des attributs privés.
__Exercice 2 -__ Mettez à jour le diagramme UML en incluant la classe __Forme__ et les relations d'héritage. Seuls les attributs seront inclus pour le moment.
Enfin, on vous demande de supporter a minima pour chaque forme les méthodes suivantes :
__Exercice 3 -__ On vous demande de supporter a minima pour chaque forme les méthodes suivantes :
* `deplacement(dx, dy)`, qui effectue une translation selon un vecteur donné.
* `translation(dx, dy)`, qui effectue une translation selon un vecteur donné.
* `contient_point(x, y)`, qui renvoie `True` si et seulement si le point donné est à l'intérieur de la forme ou sur sa frontière.
* `redimension_par_points(x0, y0, x1, y1)`, qui redimensionne la forme pour faire correspondre sa [boîte englobante](https://en.wikipedia.org/wiki/Minimum_bounding_rectangle) avec celle représentée par les points donnés.
__Exercice 3 -__ Complétez le diagramme UML avec ces méthodes. Les constructeurs devront également être renseignés (méthode `__init__` en Python), ainsi que les méthodes d'affichage (méthode `__str__` en Python).
__Exercice 4 -__ Écrivez un squelette de code correspondant à votre diagramme UML, dans un fichier _formes.py_. Seuls les constructeurs devront être implémentés. À l'intérieur des autres méthodes, vous mettrez l'instruction `pass` de Python (qui ne fait rien mais vous rappelle que le code est inachevé).
# Tests unitaires (1h)
* `redimension_par_points(x0, y0, x1, y1)`, qui redimensionne la forme telle qu'elle soit incluse dans le rectangle de coins (`x0`, `y0`) et (`x1`, `y1`). Dans le cas du cercle, il faudra également qu'il soit le plus proche du premier coin. Cette méthode est utile par exemple dans [diagrams.net](https://app.diagrams.net) pour le tracé de formes par appui-déplacement de souris.
Il convient à présent de rédiger des tests, qui échoueront tant que chaque fonction ne sera pas implémentée et correcte. Dans la méthodologie _Test Driven Development_, on les écrit toujours avant le code, au début ils échouent tous, et à mesure de l'avancement du projet le nombre de tests passés augmente. Nous utiliserons le module _pytest_ présenté en cours.
Complétez le diagramme UML avec ces méthodes. Les constructeurs devront également être renseignés (méthode `__init__` en _Python_), ainsi que les méthodes d'accès (les fameux _getter_/_setter_) et d'affichage (méthode `__str__`).
## Installation de _pytest_
__Exercice 4 -__ Écrivez un squelette de code correspondant à votre diagramme UML, dans un fichier _formes.py_. Seuls les constructeurs devront être implémentés. À l'intérieur des autres méthodes, vous mettrez l'instruction `pass` de _Python_ (qui ne fait rien mais vous rappelle que le code est inachevé).
Nous allons d'abord installer _pytest_, ainsi qu'un module permettant de lancer les tests depuis l'interface de Spyder. Ouvrez le terminal d'Anaconda (sous Windows, Menu Démarrer -> Anaconda -> Anaconda Prompt, sous Linux/Mac le terminal de base suffit). Exécutez-y la commande suivante :
```sh
conda install -c spyder-ide spyder-unittest pytest
```
❗ Si vous rencontrez une erreur comme `conda: command not found`, c'est que l'exécutable `conda` n'est présent dans aucun des dossiers visités par le terminal (essayez `echo %PATH%` pour en afficher la liste sous Windows, et `echo $PATH` sous Linux/Mac). Sous Windows, vérifiez que vous ouvrez bien le terminal d'Anaconda (pas le terminal par défaut du système). Sous Linux/Mac, la commande `export PATH=~/anaconda3/bin:/usr/local/anaconda3/bin:/usr/anaconda3/bin:$PATH` va ajouter (temporairement) une liste de répertoires usuels à la liste de recherche.
---
## Implémentation des méthodes (2h30)
Une fois les modules installés, __redémarrez Spyder__ et créez un fichier _test_formes.py_ avec l'exemple de code suivant :
Créez un fichier _test_formes.py_ dans le même dossier que _formes.py_ et initialisé avec le code suivant :
```python
from formes import *
def test_heritage():
assert issubclass(Rectangle, Forme)
assert issubclass(Ellipse, Forme)
def test_Rectangle():
r = Rectangle(10, 20, 100, 50)
str(r)
assert r.contient_point(50, 50)
assert not r.contient_point(0, 0)
r.redimension_par_points(100, 200, 1100, 700)
assert r.contient_point(500, 500)
assert not r.contient_point(50, 50)
def test_Ellipse():
e = Ellipse(60, 45, 50, 25)
str(e)
assert e.contient_point(50, 50)
assert not e.contient_point(11, 21)
e.redimension_par_points(100, 200, 1100, 700)
assert e.contient_point(500, 500)
assert not e.contient_point(101, 201)
def test_Cercle():
c = Cercle(10, 20, 30)
str(c)
assert c.contient_point(0, 0)
assert not c.contient_point(-19, -9)
c.redimension_par_points(100, 200, 1100, 700)
assert c.contient_point(500, 500)
assert not c.contient_point(599, 500)
if __name__ == '__main__':
test_Rectangle()
test_Ellipse()
test_Cercle()
```
__Exécutez ce fichier__ dans Spyder (même s'il ne fait rien), ce qui a pour effet d'initialiser le répertoire courant de Spyder à votre répertoire de travail. Allez ensuite dans le menu Run -> Run unit tests, pour configurer le module _spyder-unittest_.
<center>![](figures/spyder-unittest.png)</center>
Sélectionnez _pytest_, vérifiez que le dossier indiqué correspond à votre dossier de travail (celui contenant les fichiers _formes.py_ et _test_formes.py_), et validez. Un nouvel onglet _Unit testing_ apparaît dans l'espace en haut à droite, avec un bouton _Run tests_. Lorsque vous cliquez dessus :
La commande `assert` de _Python_ permet de spécifier une assertion (une condition qui doit toujours être vraie) à un point du programme. Elle sert, avant un bloc de code, à en documenter les prérequis et, après un bloc de code, à en vérifier les résultats. Son échec signifie alors un bug du programme. `assert` reçoit une expression (comme ce qu'on passe à `if`), et vérifie son résultat :
* _pytest_ cherche (dans le dossier que vous venez de configurer) tous les fichiers de la forme _test\_\*.py_ et _\*\_test.py_.
* Dans chacun de ces fichiers, _pytest_ exécute toutes les fonctions préfixées par `test`.
* Chaque test qui s'exécute sans déclencher d'exception est considéré valide.
* La fonction `test_heritage` dans le fichier _test_formes.py_ correspond à ces critères, donc elle est exécutée et son résultat contribue à un test "passé".
* Si `True`, l'assertion est vraie donc pas de problème, `assert` ne fait rien.
* Si `False`, l'assertion est fausse donc une exception `AssertionError` est déclenchée.
* Si l'expression renvoie une autre valeur, celle-ci est convertie en booléen pour se ramener aux deux cas précédents.
<center>![](figures/spyder-tests.png)</center>
La vérification de cette condition est faite une fois au moment de son exécution (l'assertion ne sera pas valide dans le reste du programme). Dans _test_formes.py_, on utilise `assert` pour tester une fonctionnalité qui n'est pas encore implémentée, l'exécution de ce fichier échouera tant que les méthodes de seront pas codées. À l'issue de cette partie, elle ne devra renvoyer plus aucune erreur !
## Définition des tests
__Exercice 5 -__ Implémentez les méthodes d'affichage (`__str__`) de chacune des classes dans _formes.py_. Vous pourrez vérifier leur bon fonctionnement en exécutant _formes.py_ (bouton `Run File - F5`), puis par exemple avec une commande `print(Rectangle(0, 0, 10, 10))` dans la console _IPython_.
__Exercice 5 -__ Dans le fichier _test_formes.py_, ajoutez une fonction `test_Rectangle_contient_point()` qui instancie un __Rectangle__ avec des coordonnées de votre choix, et vérifie avec `assert` que la méthode `contient_point` renvoie le bon résultat pour différentes coordonnées. L'exécution des tests doit échouer puisque votre code est encore vide.
Pour l'exercice suivant on vous donne un exemple d'implémentation de la méthode `contient_point` pour la classe __Rectangle__. La classe __Forme__ a été omise pour réduire la taille du code (mais dans votre code elle devra bien être présente).
__Exercice 6 -__ Implémentez les méthodes d'accès (_getter_/_setter_) pour les champs privés de chacune des classes. Pour vérifier que les champs sont bien privés, le code suivant __doit__ échouer avec une erreur de type `AttributeError` :
```python
class Rectangle:
def __init__(self, x, y, l, h):
self.x = x
self.y = y
self.__l = l
self.__h = h
def contient_point(self, x, y):
return self.x < x < self.__l or \
self.y < y < self.__h
r = Rectangle(0, 0, 10, 10)
print(r.__x, r.__y, r.__w, r.__h)
```
__Exercice 6 -__ Cette méthode est buggée. Comment la corriger ? Vos tests l'avaient-ils repéré ? Si ce n'est pas le cas, trouvez les coordonnées qui donnent un mauvais résultat et ajoutez-les en tests dans la fonction `test_Rectangle_contient_point`.
__Exercice 7 -__ Implémentez les méthodes `contient_point` des deux sous-classes. Vous vérifierez que les deux premiers `assert` des méthodes de test ne déclenchent pas d'erreur.
__Exercice 8 -__ Implémentez les méthodes `redimension_par_points` de chacune des sous-classes. Le fichier _test_formes.py_ doit à présent s'exécuter sans problème.
__Exercice 9 -__ Exécutez ce test sur votre code, et corrigez les éventuels bugs. Représentez ensuite, dans un logiciel de dessin (ex. [diagrams.net](https://app.diagrams.net)), le rectangle et les positions des points qui sont testés. Quels bugs sont visés par chacun de ces tests ?
# Implémentation des méthodes (2h)
__Exercice 10 -__ Dessinez une ellipse dans votre logiciel de dessin, et représentez tous les points qu'il convient de tester avec `contient_point`. Pour chaque point (ou groupe de points), indiquez le type de bug qu'il vise en particulier. Implémentez ces tests dans _test_formes.py_.
__Exercice 7 -__ Implémentez les méthodes d'affichage (`__str__`) de chacune des classes. Il ne sera pas nécessaire d'écrire des tests pour ces méthodes.
__Exercice 11 -__ Dans le cas du cercle, la différence principale avec l'ellipse est la méthode `redimension_par_points` qui nécessite de placer le centre du cercle au plus près du premier point. Proposez des tests qui permettent de vérifier que la méthode positionne toujours correctement le cercle dans la boîte englobante d'entrée.
__Exercice 8 -__ Implémentez les méthodes d'accès getter/setter pour les champs privés de chacune des classes. À l'aide de [pytest.raises](https://docs.pytest.org/en/stable/assert.html#assertions-about-expected-exceptions), vous testerez le déclenchement d'erreurs si on essaie d'accéder directement aux attributs.
---
## Schéma UML
__Exercice 9 -__ Implémentez les méthodes `contient_point` des deux sous-classes restantes. Vous fournirez pour chacune une fonction de tests avec des jeux de coordonnées pertinents.
Cet exercice a été proposé lors de l'examen S6, année 2020-2021.
> La société « L’atelier des chefs » propose des cours de cuisine. Un cours réunit pendant 2h un chef et 2 à 5 apprentis. Pendant un cours, le chef propose une recette du catalogue (Moussaka, Fish and Ships, Osso buco, Tajine marocain, Soupe chinoise…) et aide les apprentis à réaliser cette recette dans le temps imparti. Dans un esprit de convivialité, tout le monde s’appelle par son prénom.
> Ils réalisent ensemble une recette composée d’ingrédients :
> - une seule viande (parmi porc, veau, poulet) ;
> - un ou plusieurs légumes (parmi carotte, choux, potiron) ;
> - une ou plusieurs épices (parmi sel, poivre, curcuma).
__Exercice 10 -__ Implémentez les méthodes `redimension_par_points` de chacune des sous-classes. Vous fournirez également des tests validant leur fonctionnement quels que soient les points en entrée.
> Dégager les éléments principaux de cet énoncé et réaliser un diagramme de classes UML permettant de modéliser un cours de cuisine. Il n’est pas nécessaire de préciser les attributs, mais préciser les cardinalités s’il y a lieu.
### Consignes pour le rendu (BE #3 - INF-TC2)
Ce BE est le premier devoir à rendre concernant INF-TC2. Le compte-rendu (CR) de votre travail devra être déposé sur _Pedagogie1_, sur l'espace de dépôt spécifique à votre groupe. Et cela dans un **délai d'une semaine après la dernière séance consacrée à ce BE** (délai de rigueur, aucun travail accepté au delà de cette date). Cette semaine de délai ne tient pas compte d'éventuelles vacances. Si vous avez un doute, le plus simple est de contrôler la date pour votre groupe sur _Pedagogie1_.
**Consignes:**
> - Le travail peut être individuel ou en binôme. Si vous travaillez en binôme, **un seul dépôt suffit !**.
> - Le dépôt consistera en une unique archive (zip, rar ou tgz) contenant l'ensemble des fichiers suivants :
- La base de données *Hotellerie.db*, après exécution des requêtes de l'énoncé.
- Le fichier _HotelDB.py_, contenant la classe **HotelDB** et un programme principal permettant de rejouer l'ensemble des requêtes de cet énoncé.
- La base de données de votre choix pour les 2 requêtes libres (si différentes de *Hotellerie.db*) ; Si elle est très volumineuse, donnez uniquement le chemin de téléchargement dans votre rapport). Ajoutez le fichier _Python_ qui met en œuvre les requêtes que vous aurez imaginées.
- Un rapport (format _pdf_ exclusivement) contenant
- une en-tête où devront figurer le nom des élèves, leur numéro de groupe, le nom de l'encadrant ainsi que le titre du BE.
- des commentaires sur la programmation de chacune des requêtes et les résultats obtenus.
- tout diagramme, toute figure ou toute explication que vous jugerez utile, mais dans un **nombre de pages limité à 10** (il n'est pas demandé de rédiger 10 pages, c'est une limite à ne pas dépasser !).
> - L'archive devra nécessairement porter le nom suivant : *nom1-BE3.zip* ou *nom1-nom2-BE3.zip* (pour les étourdis, pensez à remplacer *nom1* et *nom2* par vos propres noms :-) )
**Critères d'évaluation du travail**
Voici une liste (non exhaustive) de critères qui permettront à vos encadrants d'évaluer vos requêtes :
> - **Qualité du rapport** : apparence visuelle globale (lisibilité), orthographe, structure du rapport claire et cohérente. Qualité des représentations graphique (légendes, label sur les axes...).
> - **Qualité de l'API** : choix des noms et arguments des méthodes, pas de requêtes SQL hors de la classe, code d'affichage des texte et des graphiques forcément en dehors des méthodes-requêtes.
> - **Qualité du code** : est-ce qu'il fonctionne sans erreur, le code est-il suffisamment commenté et aéré ? Tests multiples avec des paramètres inattendus ou erronés. Interception des erreurs potentielles par des exceptions.
> - **Qualité des requêtes** : originalité et justification de l'intérêt, difficulté technique, valorisation dans le rapport.
\ No newline at end of file
# Quelques exemples d'usage de la librairie matplotlib
# De nombreux exemples supplémentaires à cette adresse: https://matplotlib.org/gallery/index.html
# Remarque : N'oubliez pas plt.close()! après chaque figure
import matplotlib.pyplot as plt
import numpy as np
if __name__ == '__main__':
## function
#############################################
x = np.linspace(0, 2, 100) # Generate evenly spaced numbers
fig, ax = plt.subplots() # Create a figure and an axis.
ax.plot(x, x, label='linear') # Plot some data on the axes.
ax.plot(x, x**2, label='quadratic') # Plot more data on the axes...
ax.plot(x, x**3, label='cubic') # ... and some more.
ax.set_xlabel('x label') # Add an x-label to the axes.
ax.set_ylabel('y label') # Add a y-label to the axes.
ax.set_title("Simple Plot") # Add a title to the axes.
ax.legend() # Add a legend.
#plt.show() # Display the plot (close to continue)
plt.savefig('mpl_plot.png') # Save the plot on the HD (current directory)
plt.close() # Close the fig
## scatter data
#############################################
data1, data2 = np.random.randn(2, 100) # Draw random samples
fig, ax = plt.subplots(1, 1) # Create a figure and an axis.
ax.plot(data1, data2, marker='x') # Scatter plot
plt.savefig('mpl_scatter.png') # Save the plot on the HD
plt.close() # Close the fig
## error bar
# https://matplotlib.org/api/_as_gen/matplotlib.pyplot.errorbar.html
#############################################
x = np.linspace(0, 10, 50)
dy = 0.8
y = np.sin(x) + dy * np.random.randn(50)
plt.errorbar(x, y, yerr=dy, fmt='o', color='black', ecolor='lightgray', elinewidth=3, capsize=0);
plt.savefig('mpl_errorbar.png')
plt.close()
## histogram (avec personalisation)
# https://matplotlib.org/3.3.1/api/_as_gen/matplotlib.pyplot.hist.html
#############################################
x = np.random.randn(1000)
plt.style.use('classic')
fig=plt.figure(figsize=(5,3))
ax = plt.axes(facecolor='#E6E6E6')
# Afficher les ticks en dessous de l'axe
ax.set_axisbelow(True)
# Cadre en blanc
plt.grid(color='w', linestyle='solid')
# Cacher le cadre
# ax.spines contient les lignes qui entourent la zone où les
# données sont affichées.
for spine in ax.spines.values():
spine.set_visible(False)
# Cacher les marqueurs en haut et à droite
ax.xaxis.tick_bottom()
ax.yaxis.tick_left()
# Nous pouvons personnaliser les étiquettes des marqueurs
# et leur appliquer une rotation
marqueurs = [-3, -2, -1, 0, 1, 2]
xtick_labels = ['A', 'B', 'C', 'D', 'E', 'F']
plt.xticks(marqueurs, xtick_labels, rotation=30)
# Changer les couleur des marqueurs
ax.tick_params(colors='gray', direction='out')
for tick in ax.get_xticklabels():
tick.set_color('gray')
for tick in ax.get_yticklabels():
tick.set_color('gray')
# Changer les couleurs des barres
ax.hist(x, edgecolor='#E6E6E6', color='#EE6666')
plt.savefig('mpl_histopersonalise.png')
plt.close()
seance3_4h/figures/DBBrowser.png

266 KiB

seance3_4h/figures/schema_bdd_hotellerie.png

26.2 KiB

File added
**Sommaire**
[[_TOC_]]
# BE #3 : Exceptions et Base de données SQL
L'objectif principal de ce BE concerne l'utilisation **des exceptions** ([lien](https://docs.python.org/fr/3/tutorial/errors.html) officiel, [tuto _devstory_](https://devstory.net/11421/python-exception-handling) sur les exceptions) pour améliorer la robustesse d'un code. Pour expérimenter ce concept clé, nous nous servirons du prétexte de la manipulation de bases de données à l'aide de requêtes _SQL_ (_Structured Query Language_), écrites en _Python_. Ce BE est composé de trois parties :
1. **La première partie** (durée: 45 min.) présente quelques commandes élémentaires pour interroger une base _SQL_ à partir de _Python_;
1. **La seconde partie** (durée: 75 min.) permet de découvrir comment manipuler une base de données _SQL_ en _Python_ orienté objet.
1. **La troisième partie** (durée: 120 min. et +) vous engage dans un travail plus personnalisé, pour mettre à profit vos connaissances sur la gestion des exceptions, la librairie graphique *matplotlib* et le langage d'interrogation de bases _SQL_.
Ce BE fera l'objet d'un compte-rendu (CR), seul ou en binôme. Avant de commencer, veuillez prendre connaissance des consignes concernant le rendu du travail (à respecter scrupuleusement) qui se trouvent dans le fichier [consignes_BE#3.md](./consignes_BE#3.md) (dans le même répertoire que cet énoncé). Les critères de notation y sont présentés.
---
## 1. Mini-tutoriel sur la base Hotellerie.db (45 min.)
Le système de gestion de base de données (SGBD) qui sera utilisé durant ce BE est _SQLite_. Ce système stocke une base de données dans un fichier d'extension _.sqlite_. La base de tests que nous allons utiliser dans cette partie (_hotellerie.db_) est disponible au même endroit que cet énoncé. Son schéma est le suivant :
<center><img src="figures/schema_bdd_hotellerie.png" style="width:70%"/></center>
La base est composée de 5 tables, ces tables étant composées d'un nombre variable de champs. Les champs soulignés représentent les clés primaires (ou *primary key (PK)* en anglais) de chaque table. En particulier, la clé primaire de la table **chambre** est composée des attributs *numchambre* et *numhotel* (cette dernière étant la clé primaire de la table **hotel**).
*Remarque* : Vous trouverez [ici](https://www.youtube.com/watch?v=VgTRNqn2fn0) une vidéo qui montre comment utiliser [draw.io](https://app.diagrams.net) pour créer un diagramme entité-association. Il NE vous est PAS demandé de dessiner le diagramme de cette base de données.
### 1.1 DB browser for SQLite (15 min.)
Toutes les opérations sur une base de données de ce type peuvent être effectuées en _Python_ via les classes et les méthodes présentes au sein du module _sqlite3_ ([doc officielle de la librairie](https://docs.python.org/3/library/sqlite3.html)). Pour manipuler de manière interactive le contenu de la base (créer, supprimer ou modifier des tables et des enregistrements, effectuer des requêtes _SQL_...), il existe des outils adaptés. L'outil retenu dans le cadre de ce BE s'appelle ``DB Browser for SQLite``. C'est un logiciel libre qui existe pour toutes les plate-formes : _Windows_, _MacOs_, nombreuses distributions _Linux_ et _Unix_...
- Téléchargez et installez [DB Browser for SQLite](https://sqlitebrowser.org/) en suivant les instructions d'installation adaptées à votre système d'exploitation.
- Avec ce logiciel, ouvrez la base *hotellerie.db* et naviguez dans les tables (onglet `Structure de la Base de Données`) et les enregistrements (onglet `Parcourir les données`) pour prendre connaissance de la base (telle qu'elle est schématisée ci-dessus).
<center><img src="figures/DBBrowser.png" style="width:75%"/></center>
- Dans l'onglet `Exécuter le SQL`, lancez la requête suivante
```SQL
SELECT nom, ville
FROM hotel;
```
La réponse apparaît sous forme de 12 lignes. Ça vous rappelle des choses ? Si non, alors voici quelques pointeurs pour vous rafraîchir la mémoire
- [cours tutoriel sur SQL](https://www.1keydata.com/fr/sql/)
- [SQL : sélection, jointure, regroupement, filtre](http://cerig.pagora.grenoble-inp.fr/tutoriel/bases-de-donnees/chap20.htm)
- et tant d'autres...
### 1.2 Quelques requêtes en _Python_ (15 min.)
**Attention** Avant de lancer un requête sur la bdd avec _Python_, il est fortement conseillé de fermer `DB Browser for SQLite`, sinon vous pourriez soit avoir un plantage de votre programme, soit détruire la bdd (auquel cas il vous suffirait de la télécharger à nouveau depuis ce site Web).
Nous allons à présent chercher à reproduire la requête ci-dessus en utilisant _Python_ et le package _sqlite3_. C'est une librairie objet dont la [documentation](https://docs.python.org/3/library/sqlite3.html#module-sqlite3) fournit une description des classes et des méthodes disponibles. Suivez le guide...
Le squelette typique d'un tel programme s'écrit :
```python
import sqlite3
if __name__ == '__main__':
conn = sqlite3.connect('hotellerie.db') # connexion à la bdd
# travail sur la bdd à partir du connecteur
...
conn.commit() # pour enregistrer les éventuels changements
conn.close() # pour fermer proprement l'accès à la base
```
Voici le code _Python_ permettant d'obtenir la réponse à la requête précédente :
```python
import sqlite3
if __name__ == '__main__':
conn = sqlite3.connect('hotellerie.db')
curseur = conn.cursor() # objet permettant de faire des requêtes
curseur.execute("SELECT nom, ville FROM hotel;") # requête SQL
ligne1 = curseur.fetchone() # récupère la 1ère ligne du résultat de la requête
print('ligne1 =', ligne1)
ligneAll = curseur.fetchall() # récupère toutes les lignes suivantes
print('ligneAll =', ligneAll)
conn.close()
```
_Remarques_
- La commande `conn.commit()` n'est pas nécessaire ici puisque le script est une requête en lecture ; elle ne modifie donc pas la bdd.
- Notez que la méthode `fetchone()` retire le résultat de la liste des résultats. Pour preuve: ce résultat n'est pas présent suite à l’affichage du `fetchall()`.
Copiez et exécutez ce programme ; le résultat se présente sous forme d'un tuple, ou sous forme d'une liste de tuples. Ainsi la commande suivante imprime le nom du premier hôtel qui apparaît dans la liste des résultats de la requête :
```python
print(ligneAll[0][0])
```
Voici un usage intéressant à tester :
```python
import sqlite3
if __name__ == '__main__':
conn = sqlite3.connect('hotellerie.db')
curseur = conn.cursor()
for ligne in curseur.execute("SELECT * FROM hotel WHERE etoiles=3"):
print('ligne=', ligne)
conn.close()
```
### 1.3 La gestion des exceptions (15 min.)
Les quelques lignes de code précédentes peuvent être à l'origine de nombreux problèmes lors de l'exécution :
- la base de données est introuvable;
- les noms des tables et/ou des champs dans la requête sont erronés;
- ...
Voyons maintenant comment intercepter les exceptions lancées dans ces cas de figure par les méthodes de la librairie `sqlite3`. Pour cela, veuillez d'abord copier et exécuter le programme suivant :
```python
import sqlite3
if __name__ == '__main__':
try:
conn = sqlite3.connect('hotellerie.db')
curseur = conn.cursor()
curseur.execute("SELECT nom, ville FROM hotel;")
print(curseur.fetchall())
except Exception as err: # interception d'une exception quelconque
print('err:', str(err))
print('type exception:', type(err).__name__)
finally: # fermeture de la base dans tous les cas
conn.close()
```
A priori, ce programme ne lance pas d'exception. Modifiez-le pour faire apparaître les 2 problèmes énoncés ci-dessus. Pour cela :
- Changez le nom de la bdd dans le programme, p. ex. _impossible.db_. Comment interprétez-vous le message d'erreur en observant le contenu de votre répertoire de travail ?
- Revenez au nom correct du fichier : _hotellerie.db_. Testez alors des noms erronés pour les tables, puis pour les champs.
On constate, dans chaque situation, que l'exception `sqlite3.OperationalError` est lancée, avec des messages d'information différents qui précisent le type d'erreur. Remplacez alors le code `except Exception as err:` par le code `except sqlite3.OperationalError as err:`, et vérifiez que cela fonctionne de la même manière que précédemment.
La liste des exceptions lancées par l'usage de commandes `sqlite3` est disponible en suivant [ce lien](https://docs.python.org/3/library/sqlite3.html#exceptions). Prenez le temps de lire la description des différentes exceptions. Toutes les exceptions de ce module héritent d'une classe appelée __sqlite3.Error__ (elle-même héritant de la classe de base de gestion des exceptions de _Python_ : __Exception__).
---
## 2. Classe HotelDB (75 min.)
Dans cette partie, nous allons créer un classe **HotelDB** permettant de réaliser des requêtes de lecture et de mises à jour de la base *Hotellerie.db*.
### 2.1 Requête en lecture (30 min.)
Dans un fichier *HotelDB.py*, commencez à développer la classe permettant de répondre au programme principal suivant, dont l'objectif est d'afficher le nom des hôtels 2 étoiles (notez que le nombre d'étoiles est passé en argument) :
```python
if __name__ == '__main__':
aHotelDB = HotelDB('hotellerie.db')
nbEtoiles = 2
resultat = aHotelDB.get_name_hotel_etoile(nbEtoiles)
print("Liste des noms d'hotel", nbEtoiles, "étoiles : ", resultat)
```
Le constructeur (méthode *\_\_init\_\_(...)*) se chargera d'ouvrir une connexion vers la bdd, alors que le destructeur (méthode *\_\_del\_\_(...)*, _cf_ remarque ci-dessous) se chargera de la fermer.
_Remarques_ :
- Pour fermer correctement l'accès à la base de donnée, pensez à implémenter la méthode *\_\_del\_\_(...)* (vue en cours), qui est appelée automatiquement (et de manière implicite) par _Python_ lors de la destruction des objets de la classe. Typiquement :
```python
def __del__ (self):
self.__conn.close()
```
- La méthode `get_name_hotel_etoile(...)` se charge de retourner le résultat de la requête, mais ne se charge pas d'en afficher le résultat. L'affichage est laissé au programme principal, ici essentiellement pour contrôler le résultat.
- Pensez à intercepter les exceptions de type `sqlite3.OperationalError`, comme vu précédemment, et à renvoyer dans ce cas un résultat sous la forme d'une liste vide.
__Améliorations à implémenter__ :
- Comment se comporte votre programme si on insère cet appel `aHotelDB.get_name_hotel_etoile(-1)` dans le programme principal ? **Truc:** exception standard **ValueError** ou **AssertionError**.
- Comment se comporte votre programme si on insère cet appel `aHotelDB.get_name_hotel_etoile("Hello")` ? **Truc:** exception standard **TypeError**.
Veillez à ce que ces _appels erronés_ renvoient une liste vide tout simplement.
### 2.2 Requête en écriture (45 min.)
Créer une requête permettant d'ajouter un nouveau client et de renvoyer son identifiant (c'est à dire son _numclient_). Si le client existe déjà (même nom ET même prénom), la méthode renverra son _numclient_ (on supposera qu'il n'y a pas clients homonymes). Pour cette requête, renseignez-vous sur la commande `INSERT INTO`. _Attention_ la clé primaire sera renseignée automatiquement, pas besoin de la préciser explicitement. Notez également que l'attribut _curseur.lastrowid_ permet de récupérer le _numclient_ du nouveau client.
Vérifier que le nouveau client a bien été sauvegardé dans le fichier *Hotellerie.db* :
- soit en consultant la base avec `DB Browser for SQLite`;
- soit en exécutant par 2 fois successives le même programme ; vous devriez alors retrouver le même numéro de client.
_Remarques_ :
- Pensez à quitter le logiciel `DB Browser for SQLite` avant d'exécuter votre programme.
- En cas de destruction de la base `Hotellerie.db` (les requêtes en écriture sont toujours plus dangereuses que les requêtes en lecture !), pensez à la télécharger à nouveau !
---
## 3. Requêtes libres (120 min. et +)
Dans cette dernière partie, nous vous invitons à imaginer et implémenter **DEUX (2)** requêtes originales à partir de la bdd *Hotellerie.db*, ou de tout autre bdd que vous aurez trouvée sur Internet.
Les résultats de vos requêtes devront faire l'objet d'une représentation graphique (graphe, histogramme, camembert...) en utilisant la librairie _Matplotlib_ (cf remarques ci-dessous).
Vous pouvez _justifier_ la robustesse de vos requêtes en écrivant, dans votre programme principal, plusieurs appels à vos requêtes avec des paramètres farfelus, erronés... Bonus à ceux qui développerons une exception propre !
### Liens vers quelques Bdds `sqlite`
Voici quelques exemples de sites proposant des bdd _SQLite_ gratuites :
> - Une base de données de films est téléchargeable en suivant [ce lien](https://1drv.ms/u/s!Ap1xZ3X70U50gvdIaHBrQ5uvSD-WSg?e=ncEblG). Elle est composée de 4 tables (acteur, réalisateur, film, distribution)
> - Le site [SQLite tutorial](https://www.sqlitetutorial.net/sqlite-sample-database/) propose un base de données appelée _chinook_ (_digital media store_), composée de 11 tables
> - Dans le même genre, une base de données très célèbre : [Northwind](https://cs.ulb.ac.be/public/_media/teaching/infoh303/northwind_sqlite.db.zip) (8 tables)
> - Si vous êtes fan des _Pokémon_, vous pouvez décompresser la base [veekun's Pokédex](http://veekun.com/static/pokedex/downloads/veekun-pokedex.sqlite.gz) (172 tables !)
> - Si vous êtes fan de musique, vous pouvez décompresser et utiliser la base [musicBrainz](https://matthieu-moy.fr/cours/infocpp-S3/TPL/musicBrainz.zip) (4 tables)
> - Une petite base concernant la [peinture](https://carnot.cpge.info/wp-content/uploads/2020/02/peinture.db)
> - Beaucoup plus complexe : [murder-mystery](https://forge.univ-lyon1.fr/diu-eil/bloc4/-/raw/master/3_bases_de_donnees_introduction/TP/base-sql-murder-mystery.db) (9 tables). Lire le [site original](https://github.com/NUKnightLab/sql-mysteries)
> - [Postuler à Stanford](https://forge.univ-lyon1.fr/diu-eil/bloc4/-/raw/master/3_bases_de_donnees_introduction/TP/base-stanford.db) (3 tables).
Vous pouvez également transformer des données du format `.csv` (_Comma Separated Value_) vers le format `.sqlite3`, en suivant ce [tutoriel vidéo](https://tube.ac-lyon.fr/videos/watch/85399ea5-bba0-428b-9470-2d3bb41b7de1). Toutes les données _open data_ de [data.gouv.fr](https://www.data.gouv.fr/fr/), par exemple, deviennent alors exploitables pour votre CR...
Au cas où vous opteriez pour une bdd originale, n'oubliez pas d'inclure cette base dans votre archive (si elle n'est pas trop volumineuse), et de préciser dans votre rapport le chemin pour la télécharger.
### Quelques conseils
Les deux requêtes attendues doivent être relativement sophistiquées (pas de simples `select XX from YY`).
> - Si vous optez pour la bdd `Hotellerie.db`, n'hésitez pas à visiter un site de réservation d'hôtels pour trouver des idées de requêtes intéressantes. _Attention_ : le nom d'un hôtel n'est pas une clé primaire ! Plusieurs hôtels portent le même nom. Par contre, il n'existe pas 2 hôtels de même nom dans la même ville. Pensez-y !
> - Si vous optez pour une autre base, développez une seconde classe dans un second fichier indépendant (sur le modèle de la classe **HotelDB**).
**Remarques** :
> - Vous programmerez les représentations graphiques dans le programme principal (et non pas dans la méthode qui traite la requête). En effet, quand on fait une requête sur une base de données, l'affichage graphique ne doit pas être obligatoire. C'est pour cela qu'on sépare la requête de l'affichage de son résultat (qu'il soit au format texte ou au format graphique).
> - Usage de _Matplotlib_ : À titre d'exemples, vous trouverez, à côté de cet énoncé, un fichier nommé [ex_matplotlib.py](./ex_matplotlib.py). L'exécution de ce script génère 4 figures dans le sous-répertoire *figures*. Inspirez-vous largement de ce programme pour vos propres figures.
> - *Conseil*: Évitez de vous lancer dans des requêtes avec des données géographiques, genre `trouver tous les hôtels à moins de 5 kilomètres` car l'usage de cartes géographiques dépasse les attentes de ce qui est demandé ici.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="298px" height="183px" viewBox="-0.5 -0.5 298 183" content="&lt;mxfile host=&quot;app.diagrams.net&quot; modified=&quot;2020-10-01T11:54:44.123Z&quot; agent=&quot;5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36&quot; etag=&quot;epBQuWO_AHDW0usSiURE&quot; version=&quot;13.7.7&quot; type=&quot;device&quot;&gt;&lt;diagram name=&quot;Page-1&quot; id=&quot;c4acf3e9-155e-7222-9cf6-157b1a14988f&quot;&gt;7VjbctowEP0aHmGMjGzzyCWXziRtMuk0IW+yLdtqhEWFIJCvr4RlbOFwCRDStH1htEfWSjp7tFpUs3vD2QVHo+SahZjWgBXOana/BoDrOfJXAfMMgMDOgJiTMIOsArgjLzgDmzk6ISEeayyDBGNUkJEJBixNcSAMDHHOns3PIkZDAxihGFeAuwDRKnpPQpFkqAfcAr/EJE7ymZtOO+vxUfAUczZJ9XwpS3HWM0S5G73HcYJC9lyC7LOa3eOMiaw1nPUwVayajJ2v6V0umeNU7DLgW3d2Mej3O+DL9PJh0KH864DWQUsvTsxzLnAoqdEmpj57PiuA7mKrWHm0pMW4SFjMUkSvGBtJsCnBn1iIuQ4xmggmoYUbaU4xF0SyLqFEDKkegGdEPCiPDRdqc6AnUO3+rGzMcyMNOyrw0gwJGjIVAAWeE5r7HQvOnpbxbEmkSplmUe25BGgCLzAbYsHn8gOOKRJkasoFadXFy++WQ28YkVMASx8Q24YNmA3SJwTYlulkzCY8wHpcOYxvdyUQj7GouJKN0p4KaKGTt2gG/kGa+UsFA6BzHLlsdXQ8sbjn93HyeB+A5JrDyY34UXfH9WZ7o1gKNRyumLI8Urnqh7KxEEgD5mahkYWVi2Qs2RDaq7zC9lANUGubDEfaibdOqw24Ua1rhZfFPYNm3QDz6EZckQ5o33b82193rN7U8c7iuulD94Ok7Lb2k3Iz15J2ZOeX8ImkrFyq6aaITkwyVsR9hXy8kq8QJXEq24GMJ+YSyNNaR3cMSRhm2sdj8oL8hT+lhJHazmKDsFuD/Ve1sfHkqZnwzFivLqP0LEY9YkRej5IXs+2YQdSO9pWG9uKaA1gUjfH7hM7+R0NXtxpey3UPi1buCxgSaL9D8F5NVNVjVw1dkaZ9yoKnLEnrHNw2Ura15qYoXQ6Dct+am6IoWnevWQ9I6u6uSR3umNSfi785Tcc6ij4AWMkRDtwv0dveFkfHS/Svktj6L7jdqwjv4wRnW2ZBsHeRDL3Wyn+qU0suP7eG5hwqme/6shGrxvenHJJTLNGKNEtai6QCe4wyvuixIy/AQbAsWUs9vgdbcKNiKlfR2ivHNQ8v1AIpxT+v25LSkwqA66NvkP1mZp3tzD6yFHeiiASJeic6kOQoAq+THDq+I2vho5AsaV1VLHQrRDct65RMu9uZPsfpDSdpQEbq7e0zMO2ArTS33VOy7G1nuYfSqZz4M2SL5b/Bd08X0iweW7O8Xbxl22e/AQ==&lt;/diagram&gt;&lt;/mxfile&gt;"><defs/><g><path d="M 273.5 172 L 273.5 172 L 273.5 172" fill="none" stroke="#000000" stroke-width="4" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 273.5 172 L 273.5 172 L 273.5 172 L 273.5 172 Z" fill="#000000" stroke="#000000" stroke-width="4" stroke-miterlimit="10" pointer-events="all"/><path d="M 194 172 L 194 172 L 194 172" fill="none" stroke="#000000" stroke-width="4" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 194 172 L 194 172 L 194 172 L 194 172 Z" fill="#000000" stroke="#000000" stroke-width="4" stroke-miterlimit="10" pointer-events="all"/><path d="M 196.5 111.5 L 106.41 111.5" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 98.41 111.5 L 102.41 107.5 L 106.41 111.5 L 102.41 115.5 Z" fill="#000000" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 120px; margin-left: 111px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">1</div></div></div></foreignObject><text x="111" y="123" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">1</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 121px; margin-left: 187px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">1</div></div></div></foreignObject><text x="187" y="124" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">1</text></switch></g><path d="M 48.5 99 L 48.5 36.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 48.5 26.12 L 53.5 36.12 L 43.5 36.12 Z" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><path d="M 246.5 99 L 246.5 36.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 246.5 26.12 L 251.5 36.12 L 241.5 36.12 Z" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><rect x="14" y="0" width="69" height="25" fill="#f8cecc" stroke="#b85450" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 13px; margin-left: 49px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; "><b>Tk</b></div></div></div></foreignObject><text x="49" y="16" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Tk</text></switch></g><rect x="196.5" y="99" width="100" height="25" fill="#fff2cc" stroke="#d6b656" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 112px; margin-left: 247px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; "><b>ZoneAffichage</b></div></div></div></foreignObject><text x="247" y="115" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">ZoneAffichage</text></switch></g><rect x="0" y="99" width="97" height="25" fill="#fff2cc" stroke="#d6b656" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 112px; margin-left: 49px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; "><b>FenPrincipale</b></div></div></div></foreignObject><text x="49" y="115" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">FenPrincipale</text></switch></g><rect x="212" y="0" width="69" height="25" fill="#f8cecc" stroke="#b85450" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 13px; margin-left: 247px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; "><b>Canvas</b></div></div></div></foreignObject><text x="247" y="16" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Canvas</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://desk.draw.io/support/solutions/articles/16000042487" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Viewer does not support full SVG 1.1</text></a></switch></svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="544px" height="183px" viewBox="-0.5 -0.5 544 183" content="&lt;mxfile host=&quot;app.diagrams.net&quot; modified=&quot;2020-10-01T12:24:19.841Z&quot; agent=&quot;5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36&quot; etag=&quot;7_P7bSVEtSraPxa-TKuH&quot; version=&quot;13.7.7&quot; type=&quot;device&quot;&gt;&lt;diagram name=&quot;Page-1&quot; id=&quot;c4acf3e9-155e-7222-9cf6-157b1a14988f&quot;&gt;7VpZc6M4EP41fpkqu7iPRx9JJluZmWQzu4nnDYM4NoC8Qr7y61cYySCD8Ykdb+bFhT5BC77+utXqckvuR/M7ZI39b9ABYUsSnHlLHrQkSTc08psCiwxQdCUDPBQ4GSTkwHPwDjJQZOgkcEBCsQzCEIY4GPOgDeMY2JjDLITgjL/NhaHDAWPLAyXg2bbCMvoSONjPUEPSc/wrCDyfrSxqZjYzsuw3D8FJTNeLYQyymchiZug3Jr7lwFkBkm9ach9BiLOraN4HYcoqz9jthtnVKyMQ410e+NGb3w0Hg650P/36OuyG6PswbEvUTwleMC6AQ6ihQxCO4OwmB3rLTwWpRYGMIMI+9GBshQ8QjgkoEvAfgPGCutiaYEigpRkynAKEA8I6gXwchfQBMA/wa2qxo6t0OKQLpNeDeXGwYIPY6aaOJ0MnsCKYOiAFb4OQ2U0wgm8rfyoEKVNGWUy/uQBQAu8AjABGC3IDAqGFgykvF4uqzlvdt3r0EQZkCUmgASLLakfNHqIRIskCbySBE2QD+lzRjfubwhbyAC6ZIheFb8qhpU720Yz6gTTzPxWMpGqnkctWQ6cTi3774vm/XmzJ/4bUySP+u60nbdGsFUuuhuMVU5RHTN76tThYCqSjsmGukeWIiSQhbGBqVZIPUY2UvtskGlMjxiatdtRatW4UXub3DJr3bIDcR/wQdCXzqTt6+vcZtkXq78yvdTfqF5Iyqw32lbLItEQNyWwTPpOUU5PpclMrnPBkrIn7wRqBtXxlhYEXk2ub+BMgArC01qUTUeA4mfZBErxbo6W9VAnj9HOWH6j2WuqgUhu1kZeuBObc+9Iyiq7C1SOc5+lTZGOWNd6J1NCh0qBWdP4B6LoJaMZ18id1XVvoGIquH+ctZkviJGA24LzKRFUOu7Lr8jQ9CqH9liVpmoNNLmULG3aKwuYwLM5t2CnyonX3mvWIpK7vmtTVHZP6LD/miJpwEn1I0lqO0NTDEr1sbDF0ukRfSaLyW3C7VxHG5QQnC3xBcHCRrBrK2pnq3JJjcctpTgsJ870RufDSi59vDCJLrNCSNAtac4kC+zCEaDkju4YNbHtVshZmRoaqqLWKKW1FG7ccnQ9elQqk4H9Wt/mFloqkbvY+R/bezGrbmf0FY9B13cD20z7RkSS7rlRNsqONNFILn4RkQuu6YlW9RLQoCOdkWt/O9C2IH1EQ28E47b1dA9OatJVmUz8ny8Z2lvtWPCULX0O2WJ0Gz58uHpDxM/r+4/3ent0/mX+9A+uPXrv6WNBwu6KZtsOBPZDG2hWVfCu71hnajnXGkTWFosh8vBtrJnYuYjV9zVBjjbdKYj9ss6Iu7D5Zr6KSinKrQuh0vnwG311Zs6Iun9XWQBBF11H7KGw3vkSNWUlvVTHfxNFc4s7mwq5bJnc2FxvaMit6QXVSvMTJXGUncSoclpH3PpibpYO53tjBvI7sunj+M01xsXcl5xlFFDlCL11wmxUENxHR+geOaPY3mA8d0gof0vqBIa2YawW2tlZZNRzQjOu6iL4Jw2CcXEc8q/K5+m1kmP9bKXNH/mcw+eY/&lt;/diagram&gt;&lt;/mxfile&gt;"><defs/><g><path d="M 273.5 172 L 273.5 172 L 273.5 172" fill="none" stroke="#000000" stroke-width="4" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 273.5 172 L 273.5 172 L 273.5 172 L 273.5 172 Z" fill="#000000" stroke="#000000" stroke-width="4" stroke-miterlimit="10" pointer-events="all"/><path d="M 194 172 L 194 172 L 194 172" fill="none" stroke="#000000" stroke-width="4" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 194 172 L 194 172 L 194 172 L 194 172 Z" fill="#000000" stroke="#000000" stroke-width="4" stroke-miterlimit="10" pointer-events="all"/><path d="M 196.5 111.5 L 106.41 111.5" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 98.41 111.5 L 102.41 107.5 L 106.41 111.5 L 102.41 115.5 Z" fill="#000000" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 120px; margin-left: 111px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">1</div></div></div></foreignObject><text x="111" y="123" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">1</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 121px; margin-left: 187px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">1</div></div></div></foreignObject><text x="187" y="124" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">1</text></switch></g><path d="M 48.5 99 L 48.5 36.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 48.5 26.12 L 53.5 36.12 L 43.5 36.12 Z" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><path d="M 246.5 99 L 246.5 36.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 246.5 26.12 L 251.5 36.12 L 241.5 36.12 Z" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><rect x="14" y="0" width="69" height="25" fill="#f8cecc" stroke="#b85450" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 13px; margin-left: 49px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; "><b>Tk</b></div></div></div></foreignObject><text x="49" y="16" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Tk</text></switch></g><rect x="196.5" y="99" width="100" height="25" fill="#fff2cc" stroke="#d6b656" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 112px; margin-left: 247px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; "><b>ZoneAffichage</b></div></div></div></foreignObject><text x="247" y="115" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">ZoneAffichage</text></switch></g><rect x="0" y="99" width="97" height="25" fill="#fff2cc" stroke="#d6b656" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 112px; margin-left: 49px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; "><b>FenPrincipale</b></div></div></div></foreignObject><text x="49" y="115" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">FenPrincipale</text></switch></g><rect x="212" y="0" width="69" height="25" fill="#f8cecc" stroke="#b85450" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 13px; margin-left: 247px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; "><b>Canvas</b></div></div></div></foreignObject><text x="247" y="16" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Canvas</text></switch></g><path d="M 396 111.5 L 305.91 111.5" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 297.91 111.5 L 301.91 107.5 L 305.91 111.5 L 301.91 115.5 Z" fill="#000000" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 120px; margin-left: 311px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">1</div></div></div></foreignObject><text x="311" y="123" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">1</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 121px; margin-left: 387px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">0..*</div></div></div></foreignObject><text x="387" y="124" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">0..*</text></switch></g><rect x="396" y="99" width="100" height="25" fill="#fff2cc" stroke="#d6b656" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 112px; margin-left: 446px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; "><b>Forme</b></div></div></div></foreignObject><text x="446" y="115" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Forme</text></switch></g><path d="M 383.5 25 L 415.97 89.08" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 420.49 98 L 411.51 91.34 L 420.43 86.82 Z" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><rect x="349" y="0" width="69" height="25" fill="#fff2cc" stroke="#d6b656" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 13px; margin-left: 384px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; "><b>Rectangle</b></div></div></div></foreignObject><text x="384" y="16" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Rectangle</text></switch></g><path d="M 508.5 25 L 476.03 89.08" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 471.51 98 L 471.57 86.82 L 480.49 91.34 Z" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><rect x="474" y="0" width="69" height="25" fill="#fff2cc" stroke="#d6b656" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 13px; margin-left: 509px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; "><b>Ellipse</b></div></div></div></foreignObject><text x="509" y="16" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Ellipse</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://desk.draw.io/support/solutions/articles/16000042487" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Viewer does not support full SVG 1.1</text></a></switch></svg>
\ No newline at end of file
seance4_4h/figures/interface.png

8.7 KiB

class Forme:
def __init__(self, canevas, x, y):
self.__canevas = canevas
self._item = None
self.x = x
self.y = y
def effacer(self):
self.__canevas.delete(self._item)
def deplacement(self, dx, dy):
self.__canevas.move(self._item, dx, dy)
self.x += dx
self.y += dy
class Rectangle(Forme):
def __init__(self, canevas, x, y, l, h, couleur):
Forme.__init__(self, canevas, x, y)
self._item = canevas.create_rectangle(x, y, x+l, y+h, fill=couleur)
self.__l = l
self.__h = h
def __str__(self):
return f"Rectangle d'origine {self.x},{self.y} et de dimensions {self.__l}x{self.__h}"
def get_dim(self):
return self.__l, self.__h
def set_dim(self, l, h):
self.__l = l
self.__h = h
def contient_point(self, x, y):
return self.x <= x <= self.x + self.__l and \
self.y <= y <= self.y + self.__h
def redimension_par_points(self, x0, y0, x1, y1):
self.x = min(x0, x1)
self.y = min(y0, y1)
self.__l = abs(x0 - x1)
self.__h = abs(y0 - y1)
class Ellipse(Forme):
def __init__(self, canevas, x, y, rx, ry, couleur):
Forme.__init__(self, canevas, x, y)
self._item = canevas.create_oval(x-rx, y-ry, x+rx, y+ry, fill=couleur)
self.__rx = rx
self.__ry = ry
def __str__(self):
return f"Ellipse de centre {self.x},{self.y} et de rayons {self.__rx}x{self.__ry}"
def get_dim(self):
return self.__rx, self.__ry
def set_dim(self, rx, ry):
self.__rx = rx
self.__ry = ry
def contient_point(self, x, y):
return ((x - self.x) / self.__rx) ** 2 + ((y - self.y) / self.__ry) ** 2 <= 1
def redimension_par_points(self, x0, y0, x1, y1):
self.x = (x0 + x1) // 2
self.y = (y0 + y1) // 2
self.__rx = abs(x0 - x1) / 2
self.__ry = abs(y0 - y1) / 2
**Sommaire**
[[_TOC_]]
# BE #4 : Application de dessin vectoriel
L'objectif de ce BE est d'apprendre à manipuler quelques composants du module _Python_ _Tkinter_ permettant de créer des interfaces graphiques. Vous allez créer une application simple de dessin vectoriel, qui permettra de tracer à la souris les formes définies dans le BE #2.
---
## Quelques éléments sur _Tkinter_ (45 min.)
Le module _Tkinter_ (_"Tk interface"_) permet de créer des interfaces graphiques. Il contient de nombreux composants graphiques (ou _widgets_), tels que les boutons (classe __Button__), les cases à cocher (classe __CheckButton__), les étiquettes (classe __Label__), les zones d'entrée de texte (classe __Entry__), les menus (classe __Menu__), ou les zones de dessin (classe __Canvas__).
Durant ce BE, nous vous recommandons de conserver le lien vers une [documentation sur _Tkinter_]( https://python.doctor/page-tkinter-interface-graphique-python-tutoriel) ouverte dans un onglet de votre navigateur. Elle contient des exemples de code qui vous seront utiles pour utiliser chacun des _widgets_.
Voici un premier exemple de code _Tkinter_ :
```python
from tkinter import *
from random import randint
class FenPrincipale(Tk):
def __init__(self):
Tk.__init__(self)
# paramètres de la fenêtre
self.title('Tirage aléatoire')
self.geometry('300x100+400+400')
# constitution de l'arbre de scène
boutonLancer = Button(self, text='Tirage')
boutonLancer.pack(side=LEFT, padx=5, pady=5)
self.__texteResultat = StringVar()
labelResultat = Label(self, textvariable=self.__texteResultat)
labelResultat.pack(side=LEFT, padx=5, pady=5)
boutonQuitter = Button(self, text='Quitter')
boutonQuitter.pack(side=LEFT, padx=5, pady=5)
# association des widgets aux fonctions
boutonLancer.config(command=self.tirage) # appel "callback" (pas de parenthèses !)
boutonQuitter.config(command=self.quit) # idem
# tire un entier au hasard et l'affiche dans self.__texteResultat
def tirage(self):
nb = randint(1, 100)
self.__texteResultat.set('Nombre : ' + str(nb))
if __name__ == '__main__':
app = FenPrincipale()
app.mainloop()
```
*Remarque :* La méthode `pack(...)` est utilisée pour organiser les différents éléments dans la fenêtre.
__Exercice 1 -__ Copiez le code suivant dans un fichier appelé *Exo1.py* et exécutez-le pour observer le résultat.
__Attention, utilisateurs de Mac__ : l'association _Spyder_+_Tkinter_ ne fonctionne pas bien sous Mac ! Lorsque vous quitterez l'interface (par le biais du bouton _Quitter_), la fenêtre va se bloquer (_freeze_). Deux solutions:
- soit vous forcez l'application à s'arrêter à chaque fois (utilisez le menu contextuel sur l'icône de l'application concernée dans la barre d'outils);
- soit vous exécutez votre programme en ligne de commande. Pour cela, ouvrez un `Terminal` dans le répertoire de travail (clic-droit sur le répertoire → Nouveau terminal au dossier). Puis lancez le programme exécutant la commande : `python3 Exo1.py`. Vous devriez pouvoir quitter l'application sans difficulté. N'oubliez pas de sauvegarder votre fichier sous _Spyder_ avant toute exécution de cette manière !
Prenez le temps d'étudier cet exemple, et répondez aux questions suivantes :
* Combien d'éléments contient l'arbre de scène ?
* Que se passe-t-il lorsqu'on clique sur le bouton `Tirage` ?
* Comment peut-on inverser les positions des deux boutons ?
* Comment peut-on augmenter l'espace à gauche et à droite du label ?
* Comment peut-on colorier le texte du label en rouge ?
---
## Squelette de l'application de dessin (60 min.)
On souhaite obtenir l'interface ci-dessous, dans laquelle les utilisateurs sélectionneront le type de forme à dessiner avec les boutons, et créeront une forme en cliquant dans la zone située sous la barre d'outils (_widget_ __Canvas__ de _Tkinter_). On a donné une couleur grise au fond de la fenêtre pour vous aider à déterminer les différents _widgets_ présents.
<center><img src="figures/interface.png" style="width:60%"/></center>
Une pratique courante dans les interfaces graphiques est d'intégrer le code de l'application dans l'arbre de scène, en créant des classes qui s'y intégreront comme des nœuds. Ces classes héritent des classes de _Tkinter_ (pour être autorisées à les remplacer dans l'arbre), et nous leur ajouterons des attributs et méthodes spécifiques à leurs responsabilités dans l'application de dessin. Nous allons ainsi introduire deux classes :
* la classe __ZoneAffichage__, qui hérite de __Canvas__ et gère toutes les opérations de dessin spécifiques à votre application.
* la classe __FenPrincipale__, qui hérite de __Tk__ et gère l'initialisation de l'arbre de scène et des _callbacks_ des _widgets_.
Voici le diagramme UML correspondant :
<center><img src="figures/Fenetre_ZoneAffichage_1.svg" style="width:50%"/></center>
En rouge figure les classes de la librairie _Tkinter_, et en jaune vos propres classes.
__Exercice 2 -__ Dessinez l'arbre de scène correspondant à la capture d'écran. Pour chaque nœud vous indiquerez la classe et, s'il y a lieu, sa classe parente également.
__Exercice 3 -__ Complétez le code ci-dessous avec l'initialisation de votre arbre de scène. Vous utiliserez une instance de __ZoneAffichage__ pour implémenter le canevas. À ce stade, on ne vous demande pas de programmer les actions associées aux boutons, mais uniquement de mettre en place le design de l'interface. Vous trouverez des exemples d'utilisation de chacun des _widgets_ dans la documentation référencée plus haut.
```python
from tkinter import *
class ZoneAffichage(Canvas):
def __init__(self, parent, largeur, hauteur):
Canvas.__init__(self, parent, width=largeur, height=hauteur)
class FenPrincipale(Tk):
def __init__(self):
Tk.__init__(self)
self.configure(bg="grey")
# L'initialisation de l'arbre de scène se fait ici
if __name__ == "__main__":
fen = FenPrincipale()
fen.mainloop()
```
---
## Dessin de formes dans le canevas (75 min.)
Vous trouverez dans le dossier de ce BE le fichier [formes.py](formes.py) développé durant le BE #2. Nous avons agrémenté les classes __Rectangle__ et __Ellipse__ pour qu'elles reçoivent un canevas en argument et se dessinent dessus lors de leur initialisation. La classe __Forme__ est maintenant dotée d'une méthode `effacer()` qui supprimera la forme du canevas.
Copiez ce fichier dans votre répertoire de travail.
Ces classes seront intégrées selon le diagramme UML suivant :
<center><img src="figures/Fenetre_ZoneAffichage_2.svg" style="width:90%"/></center>
__Exercice 4 -__ À l'aide de la méthode `bind` vue en cours, reliez les clics de la souris dans le canevas (événements `<ButtonRelease-1>`) à une nouvelle méthode de __ZoneAffichage__ qui imprime les coordonnées de chaque clic avec `print`.
__Exercice 5 -__ Modifiez cette méthode pour créer un nouveau __Rectangle__ centré sur la souris chaque fois que la méthode est exécutée (à ce stade choisissez des dimensions arbitraires). N'oubliez pas de stocker ce rectangle dans __ZoneAffichage__ !
__Exercice 6 -__ Lorsqu'on clique sur le bouton `Ellipse`, l'outil "Ellipse" est sélectionné et tous les futurs clics dans le canevas doivent créer une nouvelle __Ellipse__ (de dimension fixe quelconque). Lorsqu'on clique ensuite sur le bouton `Rectangle`, les clics suivants créeront un __Rectangle__. L'outil sélectionné par défaut est "Rectangle". Modifiez votre code pour implémenter ce comportement.
---
## Opérations de dessin supplémentaires (60 min.)
Nous allons à présent intégrer deux commandes simples dans l'application de dessin :
* Lorsqu'on clique sur une forme en maintenant la touche CTRL enfoncée, elle doit s'effacer du canevas.
* Lorsqu'on clique sur le bouton _Couleur_, un sélecteur de couleurs apparaît pour choisir la couleur de l'outil de dessin.
__Exercice 7 -__ Implémentez l'effacement des formes avec CTRL-clic (événement `<Control-ButtonRelease-1>`). Vous pourrez faire appel aux méthodes `contient_point(...)` des classes __Rectangle__ et __Ellipse__ pour déterminer si la position de la souris au moment de l'événement est dans le périmètre d'une forme donnée, ainsi qu'à la méthode `effacer(...)` de la classe __Forme__.
__Exercice 8 -__ À l'aide du module _colorchooser_ de _Tkinter_ (```from tkinter import colorchooser```), liez les clics sur le bouton _Couleur_ à l'affichage d'un sélecteur de couleur, et utilisez la couleur renvoyée pour tous les ajouts de formes suivants.
---
## Exercices bonus
Il n'y a pas d'ordre prédéfini pour ces trois exercices supplémentaires, choisissez celui dont la fonctionnalité vous semble la plus intéressante.
__Bonus 1 -__ Dans tout programme de dessin respectable, on doit pouvoir dessiner des formes de tailles arbitraires (pas prédéfinies comme précédemment). À l'aide des types d'événements `<Button-1>`, `<B1-Motion>` et `<ButtonRelease-1>`, faites qu'un mouvement de souris avec le bouton enfoncé dessine une forme en tirant ses coins (lorsqu'il ne déplace pas une forme existante). Vous pourrez utiliser les méthodes `redimension_par_points(...)` des classes __Rectangle__ et __Ellipse__.
__Bonus 2 -__ Il serait aussi pratique de pouvoir déplacer les formes présentes sur le canevas. À l'aide des types d'événements `<Button-1>`, `<B1-Motion>` et `<ButtonRelease-1>`, implémentez la translation des formes lors des actions d'appui-déplacement de la souris. Comment faire pour qu'elles n'interfèrent pas avec la création de nouvelles formes ?
__Bonus 3 -__ Maintenant que votre programme de dessin vectoriel est fonctionnel, on doit pouvoir exporter chaque image produite dans un fichier. On utilise pour cela le format SVG, qui est un fichier texte contenant des instructions de dessin. Il suffit d'écrire `<svg viewBox="0 0 600 400" xmlns="http://www.w3.org/2000/svg">` au début du fichier, `</svg>` à la fin, et d'insérer des balises [`rect`](https://developer.mozilla.org/fr/docs/Web/SVG/Element/rect) et [`ellipse`](https://developer.mozilla.org/fr/docs/Web/SVG/Element/ellipse) entre les deux. Pour obtenir un dessin coloré, vous pouvez insérer à l'intérieur des 2 balises, la chaîne `style="fill: brown"` et remplacer `brown` par la couleur de votre forme. C'est maintenant à vous de jouer !
### Consignes pour le rendu (BE #5 - INF-TC2)
Ce BE est le second devoir à rendre concernant INF-TC2. Le compte-rendu (CR) de votre travail devra être déposé sur `Pedagogie1`, sur l'espace de dépôt spécifique à votre groupe. Et cela dans un **délai d'une (1) semaines après la dernière séance consacrée** (délai de rigueur, aucun travail accepté au delà de cette date). Cette semaine ne tient pas compte d'éventuelles vacances. Si vous avez un doute, le plus simple est de contrôler la date pour votre groupe sur `Pedagogie1`.
**Consignes:**
- Le travail peut être individuel ou en binôme. Si vous travaillez en binôme, **un seul dépôt suffit !**.
- Le dépôt consistera en une unique archive (zip, rar) contenant l'ensemble des fichiers suivants :
- Le fichier _Python_, appelé *Pendu.py*, contenant toutes les classes (**FenPrincipale**, **ZoneAffichage** et **MonBoutonLettre**), et un programme principal permettant de lancer l'application.
- Le fichier *mots.txt* et la classe *formes.py* (même s'ils n'ont pas été modifiés).
- un rapport (format _word_, _pdf_, ou mieux encore _markdown_ !) contenant
- Une en-tête où devront figurer le nom des élèves, leur numéro de groupe, le nom de l'encadrant ainsi que le titre du BE.
- Un diagramme de classes UML complet de votre application (pensez à utiliser [diagrams](https://app.diagrams.net) !), avec les liens entre les classes, les cardinalités, les attributs (privés ou publics), et les méthodes (elles-mêmes publiques ou privées).
- Une présentation du code de la DERNIÈRE PARTIE UNIQUEMENT de votre travail (partie intitulée _Améliorations du jeu_). Vous pouvez utilisez des explications textuelles, des diagrammes, des copies d'écran...
- L'archive devra nécessairement porter le nom suivant : *nom1-BE5.zip* ou *nom1-nom2-BE5.zip* (pour les étourdis, pensez à remplacer *nom1* et *nom2* par vos propres noms :-) )
**Critères d'évaluation du travail**
Voici une liste (non exhaustive) de critères qui permettront à vos encadrants d'évaluer vos requêtes :
- **Qualité du rapport** : apparence visuelle globale, orthographe, structure du rapport claire et cohérente. Présence et complétude du diagramme de classes. Qualité des représentations graphiques.
- **Qualité du code** : est-ce qu'il fonctionne sans erreur, le code est-il suffisamment commenté et aéré ? Tests multiples avec des paramètres inattendus ou erronés. Interception des erreurs par des exceptions.
- **Qualité de l'interface** : aspect visuel de l'interface graphique.
\ No newline at end of file
<mxfile host="Electron" modified="2020-10-07T05:23:43.840Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.7.3 Chrome/85.0.4183.98 Electron/10.1.2 Safari/537.36" etag="rDyIsUkNxe0FlCVNYQt9" version="13.7.3" type="device"><diagram id="uKeeR9P6dBSU_i-VrJ_J" name="Page-1">7Zpbl5owEIB/jY/dQxKuj9W9dNttz7b2nO72LUIEukhsDF766xskgIArispq65PJZHKbfCQziR3UG83vGB57n6lDgg5UnHkHXXcgBEDRxU8sWSQS0wSJwGW+I5VyQd//Q6RQkdLId8ikoMgpDbg/LgptGobE5gUZZozOimpDGhR7HWOXVAR9GwdV6Q/f4Z6cBTRy+Qfiu17aM9CtpGSEU2U5k4mHHTpbEaGbDuoxSnmSGs17JIiNl9olqXf7Smk2MEZCvk2F+08fo6l6P3W+uebjg92fLVz3nWxlwhfphBmNQofEdZQO6lLGPerSEAcPlI6FEAjhL8L5Qi4VjjgVIo+PAllK5j5/ktXj9POK/Hq+UnC9SDMhZ4unVC3OPK+W5JWWubTWhDP6ki2KMGe3apF0ejRiNtlgBijJwswlfIMeSvSIU4BG2vuO0BERIxQKjASY+9MiQ1ii6GZ6+WqJhFywHRZPjnqKg0j21IF6IMbfHYiEGycYtv2QZGKWylPJkApTxV9PQNmyCf13RBMFpCjGcDhcFSV1v7+ktcWokwaKjVa7EYqDXLaRuJnnc9If4+V6zcSmUqQLB74birQt1piwtRgM/SDoZRNCYg7QtjPNlRJHH+iavgmcKWGczDcutSxFSH7m6T4ns7N80zCkyFvZL1TlSGygejbElslJSCJ2WDxuGR6RMyOE6K8QYlgDRTkMIQAUCcmOuLdCRK1H5AuNpqIlksyd+wfeSboR5zQ8K1YcjZiOuo4VEw6QfqjdpIgKglVUgNImK1o9K18jn8dGPCwh9ILIK9uJUc9Iq9uJXrFz7Cb1ZTakwg0R/si+3mXV+pm/eQW13V1O5Urbxels7mJaW7qY6alwOB9TVn2k/vL7k/yosMSPUgIjGamsVWIjG0ZzXIw3c1B6OJyKSZ7TltKOhwL1E/NQzHpGRpQflo4HPIivMM4IjnbOG007MTisS4RzYvsHsk4MkbT/TYy8v4Q07YQ0oN5fNVuFA9TD8fMCRytwqPqpwbHubrUc3oTO+/iFIY9uHDzxlksBNgUtqtR8xHG8HC51oLLxArs2GFixk7bGTqls35hBMYrLVLZ/EgVVYoZqQ2pNQ0cOPgCq+Y72efdoIw59i8cPY0sW94QMlrwIoDWEDBjwSis6JLBlzNRjYmb8m5hZ7WCW3XekmOlNMTMrmKGWMdOOhxlowNipE2a2RFjJ481ubHcmzKoQ1vZ5Wb3c/X/PS7QlZmpLmJUucoHZEDPhg5YxKx+9x8bMuJyXO2OmtYRZ6U0SWE0xAxXMykfvsTEzL5jt/i61bSh6YPcflt+l9nD/ywdwY85ENv/jXqKe//0R3fwF</diagram></mxfile>
\ No newline at end of file
seance5_6h/figures/Arbre_scene_pendu.png

26 KiB

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="592px" height="282px" viewBox="-0.5 -0.5 592 282" content="&lt;mxfile host=&quot;Electron&quot; modified=&quot;2020-10-23T14:24:20.314Z&quot; agent=&quot;5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.7.9 Chrome/85.0.4183.121 Electron/10.1.3 Safari/537.36&quot; etag=&quot;j2wcMG27qSD1AfmOCcUu&quot; version=&quot;13.7.9&quot; type=&quot;device&quot;&gt;&lt;diagram id=&quot;uKeeR9P6dBSU_i-VrJ_J&quot; name=&quot;Page-1&quot;&gt;7Zpbl5owEIB/jY/dQxKuj9W9dNttz7b2nO72LUIEukhsDF766xskgIArispq65PJZHKbfCQziR3UG83vGB57n6lDgg5UnHkHXXcgBEDRxU8sWSQSU7ESgct8Ryrlgr7/h0ihIqWR75BJQZFTGnB/XBTaNAyJzQsyzBidFdWGNCj2OsYuqQj6Ng6q0h++wz05C2jk8g/Ed720Z6DL+Y1wqixnMvGwQ2crInTTQT1GKU9So3mPBLHxUrsk9W5fKc0GxkjIt6lw/+ljNFXvp84313x8sPuzheu+k61M+CKdMKNR6JC4jtJBXcq4R10a4uCB0rEQAiH8RThfyKXCEadC5PFRIEvJ3OdPsnqcfl6RX89XCq4XaSbkbPGUqsWZ59WSvNIyl9aacEZfskUR5uxWLZJOj0bMJhvMACVZmLmEb9BDiR5xCtBIe98ROiJihEKBkQBzf1pkCEsU3UwvXy2RkAu2w+LJUU9xEMmeOlAPxPi7A5Fw4wTDth+STMxSeSoZUmGq+OsJKFs2of+OaKKAFMUYDoeroqTu95e0thh10kCx0Wo3QnGQyzYSN/N8TvpjvFyvmdhUinThwHdDkbbFGhO2FoOhHwS9bEJIzAHadqa5UuLoA13TN4EzJYyT+callqUIyc882+eS7CzfNAwp8lb2C1U5Ehuong2xZXISkogdFo9bhkfkzAgh+iuEGNZAUQ5DCABFQrIj7q0QUesR+UKjqWiJJHPn/oF3km7EOQ3PihVHI6ajrmPFhAOkH2o3KaKCYBUVoLTJilbPytfI57ERD0sIvSDyynZi1DPS6naiV+wcu0l9mQ2pcEOEP7Kvd1m1fuZvXkFtd5dTudJ2cTqbu5jWli5meioczseUVR+pv/z+JD8qLPGjlMBIRiprldjIhtEcF+PNHJQeDqdikue0pbTjoUD9xDwUs56REeWHpeMBD+IrjDOCo53zRtNODA7rEuGc2P6BrBNDJO1/EyPvLyFNOyENqPdXzVbhAPVw/LzA0Qocqn5qcKy7Wy2HN6HzPn5hyKMbB0+85VKATUGLKjUfcRwvh0sdqGy8wK4NBlbspK2xUyrbN2ZQjOIyle2fREGVmKHakFrT0JGDD4BqvqN93j3aiEPf4vHD2JLFPSGDJS8CaA0hAwa80ooOCWwZM/WYmBn/JmZWO5hl9x0pZnpTzMwKZqhlzLTjYQYaMHbqhJktEVbyeLMb250JsyqEtX1eVi93/9/zEm2JmdoSZqWLXGA2xEz4oGXMykfvsTEzLuflzphpLWFWepMEVlPMQAWz8tF7bMzMC2a7v0ttG4oe2P2H5XepPdz/8gHcmDORzf+4l6jnf39EN38B&lt;/diagram&gt;&lt;/mxfile&gt;" style="background-color: rgb(255, 255, 255);"><defs/><g><path d="M 301 41 L 158.06 126.76" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 152.92 129.85 L 157.72 122.3 L 158.06 126.76 L 161.84 129.16 Z" fill="#000000" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><rect x="301" y="1" width="70" height="40" fill="#fff2cc" stroke="#d6b656" stroke-width="2" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 21px; margin-left: 302px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><b>racine<br /><font color="#007fff">Tk</font><br /></b></div></div></div></foreignObject><text x="336" y="25" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">racine...</text></switch></g><rect x="81" y="131" width="70" height="40" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 151px; margin-left: 82px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><b>conteneur<br /><font color="#007fff">Frame</font><br /></b></div></div></div></foreignObject><text x="116" y="155" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">conteneur...</text></switch></g><rect x="1" y="241" width="100" height="40" fill="#d5e8d4" stroke="#82b366" stroke-width="2" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 261px; margin-left: 2px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><b>Nouvelle partie<br /><font color="#007fff">Button</font><br /></b></div></div></div></foreignObject><text x="51" y="265" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Nouvelle partie...</text></switch></g><rect x="141" y="241" width="70" height="40" fill="#d5e8d4" stroke="#82b366" stroke-width="2" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 261px; margin-left: 142px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><b>Quitter<br /><font color="#007fff">Boutton</font><br /></b></div></div></div></foreignObject><text x="176" y="265" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Quitter...</text></switch></g><path d="M 378.5 171 L 326.23 234.64" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 322.42 239.27 L 324.41 230.55 L 326.23 234.64 L 330.59 235.63 Z" fill="#000000" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><rect x="231" y="131" width="70" height="40" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 151px; margin-left: 232px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><b>conteneur<br /><font color="#007fff">Canvas</font><br /></b></div></div></div></foreignObject><text x="266" y="155" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">conteneur...</text></switch></g><rect x="521" y="131" width="70" height="40" fill="#d5e8d4" stroke="#82b366" stroke-width="2" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 151px; margin-left: 522px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><b>mot<br /><font color="#007fff">Label</font><br /></b></div></div></div></foreignObject><text x="556" y="155" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">mot...</text></switch></g><rect x="361" y="131" width="70" height="40" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 151px; margin-left: 362px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><b>conteneur<br /><font color="#007fff">Frame</font><br /></b></div></div></div></foreignObject><text x="396" y="155" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">conteneur...</text></switch></g><rect x="281" y="241" width="80" height="40" fill="#d5e8d4" stroke="#82b366" stroke-width="2" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 78px; height: 1px; padding-top: 261px; margin-left: 282px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><b>A<br /><font color="#007fff">Button</font><br /></b></div></div></div></foreignObject><text x="321" y="265" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">A...</text></switch></g><rect x="431" y="241" width="80" height="40" fill="#d5e8d4" stroke="#82b366" stroke-width="2" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 78px; height: 1px; padding-top: 261px; margin-left: 432px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><b>Z<br /><font color="#007fff">Button</font><br /></b></div></div></div></foreignObject><text x="471" y="265" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Z...</text></switch></g><path d="M 378 261 L 418 261" fill="none" stroke="#000000" stroke-width="4" stroke-miterlimit="10" stroke-dasharray="4 8" pointer-events="stroke"/><path d="M 318.5 41 L 270.15 123.89" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 267.13 129.07 L 267.7 120.14 L 270.15 123.89 L 274.61 124.17 Z" fill="#000000" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><path d="M 353.5 41 L 392.48 123.55" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 395.05 128.98 L 388.01 123.45 L 392.48 123.55 L 395.25 120.04 Z" fill="#000000" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><path d="M 371 41 L 513.94 126.76" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 519.08 129.85 L 510.16 129.16 L 513.94 126.76 L 514.28 122.3 Z" fill="#000000" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><path d="M 98.5 171 L 55.62 234.18" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 52.26 239.15 L 53.44 230.28 L 55.62 234.18 L 60.06 234.78 Z" fill="#000000" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><path d="M 133.5 171 L 171.73 233.96" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 174.84 239.09 L 167.27 234.33 L 171.73 233.96 L 174.11 230.17 Z" fill="#000000" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><path d="M 413.5 171 L 465.77 234.64" fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 469.58 239.27 L 461.41 235.63 L 465.77 234.64 L 467.59 230.55 Z" fill="#000000" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://desk.draw.io/support/solutions/articles/16000042487" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Viewer does not support full SVG 1.1</text></a></switch></svg>
\ No newline at end of file
This diff is collapsed.
seance5_6h/figures/TableSQLJeuPendu.png

14.2 KiB