building_uis.rst 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. .. _tut_building_uis:
  2. Building custom UIs
  3. ========================
  4. FlexMeasures provides its own UI (see :ref:`dashboard`), but it is a back office platform first.
  5. Most energy service companies already have their own user-facing system.
  6. We therefore made it possible to incorporate information from FlexMeasures in custom UIs.
  7. This tutorial will show how the FlexMeasures API can be used from JavaScript to extract information and display it in a browser (using HTML). We'll extract information about users, assets and even whole plots!
  8. .. contents:: Table of contents
  9. :local:
  10. :depth: 1
  11. .. note:: We'll use standard JavaScript for this tutorial, in particular the `fetch <https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch>`_ functionality, which many browsers support out-of-the-box these days. You might want to use more high-level frameworks like jQuery, Angular, React or VueJS for your frontend, of course.
  12. Get an authentication token
  13. -----------------------
  14. FlexMeasures provides the `[POST] /api/requestAuthToken <../api/v2_0.html#post--api-v2_0-requestAuthToken>`_ endpoint, as discussed in :ref:`api_auth`.
  15. Here is a JavaScript function to call it:
  16. .. code-block:: JavaScript
  17. var flexmeasures_domain = "http://localhost:5000";
  18. function getAuthToken(){
  19. return fetch(flexmeasures_domain + '/api/requestAuthToken',
  20. {
  21. method: "POST",
  22. mode: "cors",
  23. headers:
  24. {
  25. "Content-Type": "application/json",
  26. },
  27. body: JSON.stringify({"email": email, "password": password})
  28. }
  29. )
  30. .then(function(response) { return response.json(); })
  31. .then(console.log("Got auth token from FlexMeasures server ..."));
  32. }
  33. It only expects you to set ``email`` and ``password`` somewhere (you could also pass them to the function, your call). In addition, we expect here that ``flexmeasures_domain`` is set to the FlexMeasures server you interact with, for example "https://company.flexmeasures.io".
  34. We'll see how to make use of the ``getAuthToken`` function right away, keep on reading.
  35. Load user information
  36. -----------------------
  37. Let's say we are interested in a particular user's meta data. For instance, which email address do they have and which timezone are they operating in?
  38. Given we have set a variable called ``userId``, here is some code to find out and display that information in a simple HTML table:
  39. .. code-block:: html
  40. <h1>User info</h1>
  41. <p>
  42. Email address: <span id="user_email"></span>
  43. </p>
  44. <p>
  45. Time zone: <span id="user_timezone"></span>
  46. </p>
  47. .. code-block:: JavaScript
  48. function loadUserInfo(userId, authToken) {
  49. fetch(flexmeasures_domain + '/api/v2_0/user/' + userId,
  50. {
  51. method: "GET",
  52. mode: "cors",
  53. headers:
  54. {
  55. "Content-Type": "application/json",
  56. "Authorization": authToken
  57. },
  58. }
  59. )
  60. .then(console.log("Got user data from FlexMeasures server ..."))
  61. .then(function(response) { return response.json(); })
  62. .then(function(userInfo) {
  63. document.querySelector('#user_email').innerHTML = userInfo.email;
  64. document.querySelector('#user_timezone').innerHTML = userInfo.timezone;
  65. })
  66. }
  67. document.onreadystatechange = () => {
  68. if (document.readyState === 'complete') {
  69. getAuthToken()
  70. .then(function(response) {
  71. var authToken = response.auth_token;
  72. loadUserInfo(userId, authToken);
  73. })
  74. }
  75. }
  76. The result looks like this in your browser:
  77. .. image:: https://github.com/FlexMeasures/screenshots/raw/main/tut/user_info.png
  78. :align: center
  79. .. :scale: 40%
  80. From FlexMeasures, we are using the `[GET] /user <../api/v3_0.html#get--api-v3_0-user-(id)>`_ endpoint, which loads information about one user.
  81. Browse its documentation to learn about other information you could get.
  82. Load asset information
  83. -----------------------
  84. Similarly, we can load asset information. Say we have a variable ``accountId`` and we want to show which assets FlexMeasures administrates for that account.
  85. For the example below, we've used the ID of the account from our toy tutorial, see :ref:`toy tutorial<tut_toy_schedule>`.
  86. .. code-block:: html
  87. <style>
  88. #assetTable th, #assetTable td {
  89. border-right: 1px solid gray;
  90. padding-left: 5px;
  91. padding-right: 5px;
  92. }
  93. </style>
  94. .. code-block:: html
  95. <table id="assetTable">
  96. <thead>
  97. <tr>
  98. <th>Asset name</th>
  99. <th>ID</th>
  100. <th>Latitude</th>
  101. <th>Longitude</th>
  102. </tr>
  103. </thead>
  104. <tbody></tbody>
  105. </table>
  106. .. code-block:: JavaScript
  107. function loadAssets(accountId, authToken) {
  108. var params = new URLSearchParams();
  109. params.append("account_id", accountId);
  110. fetch(flexmeasures_domain + '/api/v3_0/assets?' + params.toString(),
  111. {
  112. method: "GET",
  113. mode: "cors",
  114. headers:
  115. {
  116. "Content-Type": "application/json",
  117. "Authorization": authToken
  118. },
  119. }
  120. )
  121. .then(console.log("Got asset data from FlexMeasures server ..."))
  122. .then(function(response) { return response.json(); })
  123. .then(function(rows) {
  124. rows.forEach(row => {
  125. const tbody = document.querySelector('#assetTable tbody');
  126. const tr = document.createElement('tr');
  127. tr.innerHTML = `<td>${row.name}</td><td>${row.id}</td><td>${row.latitude}</td><td>${row.longitude}</td>`;
  128. tbody.appendChild(tr);
  129. });
  130. })
  131. }
  132. document.onreadystatechange = () => {
  133. if (document.readyState === 'complete') {
  134. getAuthToken()
  135. .then(function(response) {
  136. var authToken = response.auth_token;
  137. loadAssets(accountId, authToken);
  138. })
  139. }
  140. }
  141. The result looks like this in your browser:
  142. .. image:: https://github.com/FlexMeasures/screenshots/raw/main/tut/asset_info.png
  143. :align: center
  144. .. :scale: 40%
  145. From FlexMeasures, we are using the `[GET] /assets <../api/v3_0.html#get--api-v3_0-assets>`_ endpoint, which loads a list of assets.
  146. Note how, unlike the user endpoint above, we are passing a query parameter to the API (``account_id``).
  147. We are only displaying a subset of the information which is available about assets.
  148. Browse the endpoint documentation to learn other information you could get.
  149. For a listing of public assets, replace `/api/v3_0/assets` with `/api/v3_0/assets/public`.
  150. Embedding charts
  151. ------------------------
  152. Creating charts from data can consume lots of development time.
  153. FlexMeasures can help here by delivering ready-made charts.
  154. In this tutorial, we'll embed a chart with electricity prices.
  155. First, we define a div tag for the chart and a basic layout (full width). We also load the visualization libraries we need (more about that below), and set up a custom formatter we use in FlexMeasures charts.
  156. .. code-block:: html
  157. <script src="https://d3js.org/d3.v6.min.js"></script>
  158. <script src="https://cdn.jsdelivr.net/npm/vega@5.22.1"></script>
  159. <script src="https://cdn.jsdelivr.net/npm/vega-lite@5.2.0"></script>
  160. <script src="https://cdn.jsdelivr.net/npm/vega-embed@6.20.8"></script>
  161. <script>
  162. vega.expressionFunction('quantityWithUnitFormat', function(datum, params) {
  163. return d3.format(params[0])(datum) + " " + params[1];
  164. });
  165. </script>
  166. <div id="sensor-chart" style="width: 100%;"></div>
  167. Now we define a JavaScript function to ask the FlexMeasures API for a chart and then embed it:
  168. .. code-block:: JavaScript
  169. function embedChart(params, authToken, sensorId, divId){
  170. fetch(
  171. flexmeasures_domain + '/api/dev/sensor/' + sensorId + '/chart?include_data=true&' + params.toString(),
  172. {
  173. method: "GET",
  174. mode: "cors",
  175. headers:
  176. {
  177. "Content-Type": "application/json",
  178. "Authorization": authToken
  179. }
  180. }
  181. )
  182. .then(function(response) {return response.json();})
  183. .then(function(data) {vegaEmbed(divId, data)})
  184. }
  185. This function allows us to request a chart (actually, a JSON specification of a chart that can be interpreted by vega-lite), and then embed it within a ``div`` tag of our choice.
  186. From FlexMeasures, we are using the `GET /api/dev/sensor/(id)/chart/ <../api/dev.html#get--api-dev-sensor-(id)-chart->`_ endpoint.
  187. Browse the endpoint documentation to learn more about it.
  188. .. note:: Endpoints in the developer API are still under development and are subject to change in new releases.
  189. Here are some common parameter choices for our JavaScript function:
  190. .. code-block:: JavaScript
  191. var params = new URLSearchParams();
  192. params.append("width", 400); // an integer number of pixels; without it, the chart will be scaled to the full width of the container (note that we set the div width to 100%)
  193. params.append("height", 400); // an integer number of pixels; without it, a FlexMeasures default is used
  194. params.append("event_starts_after", '2022-10-01T00:00+01'); // only fetch events from midnight October 1st
  195. params.append("event_ends_before", '2022-10-08T00:00+01'); // only fetch events until midnight October 8th
  196. params.append("beliefs_before", '2022-10-03T00:00+01'); // only fetch beliefs prior to October 3rd (time travel)
  197. As FlexMeasures uses `the Vega-Lite Grammar of Interactive Graphics <https://vega.github.io/vega-lite/>`_ internally, we also need to import this library to render the chart (see the ``script`` tags above). It's crucial to note that FlexMeasures is not transferring images across HTTP here, just information needed to render them.
  198. .. note:: It's best to match the visualization library versions you use in your frontend to those used by FlexMeasures. These are set by the FLEXMEASURES_JS_VERSIONS config (see :ref:`configuration`) with defaults kept in ``flexmeasures/utils/config_defaults``.
  199. Now let's call this function when the HTML page is opened, to embed our chart:
  200. .. code-block:: JavaScript
  201. document.onreadystatechange = () => {
  202. if (document.readyState === 'complete') {
  203. getAuthToken()
  204. .then(function(response) {
  205. var authToken = response.auth_token;
  206. var params = new URLSearchParams();
  207. params.append("event_starts_after", '2022-01-01T00:00+01');
  208. embedChart(params, authToken, 1, '#sensor-chart');
  209. })
  210. }
  211. }
  212. The parameters we pass in describe what we want to see: all data for sensor 3 since 2022.
  213. If you followed our :ref:`toy tutorial<tut_toy_schedule>` on a fresh FlexMeasures installation, sensor 1 contains market prices (authenticate with the toy-user to gain access).
  214. The result looks like this in your browser:
  215. .. image:: https://github.com/FlexMeasures/screenshots/raw/main/tut/plotting-prices.png
  216. :align: center
  217. .. :scale: 40%