|
| 1 | +# Using Charts |
| 2 | + |
| 3 | +Because of TypeCells support for React we can use all of the powerful React charting libraries. I'd like to explore two libraries in this demo: <a href="https://vega.github.io/vega-lite/" target="_blank">VegaLite</a> and <a href="https://www.chartjs.org/" target="_blank">Chart.js</a>. |
| 4 | + |
| 5 | +Let's see if we can do something interesting with some live weather data. I've found a public api called <a href="https://open-meteo.com/" target="_blank">Open-Meteo</a> that can provide us with some weather data. For this demo I entered the GPS coordinates of Amsterdam. We can use `fetch` to make the api call and then directly export it's response as a JavaScript object. |
| 6 | + |
| 7 | +```typescript |
| 8 | +// Location coordinates of Amsterdam |
| 9 | +export let lat = typecell.Input(<input type="text" disabled />, 52.3738); |
| 10 | +export let long = typecell.Input(<input type="text" disabled />, 4.891); |
| 11 | + |
| 12 | +export default ( |
| 13 | + <div> |
| 14 | + <p>GPS coords</p> |
| 15 | + {lat} <br></br> |
| 16 | + {long} |
| 17 | + </div> |
| 18 | +); |
| 19 | +``` |
| 20 | + |
| 21 | +```typescript |
| 22 | +// Make api call and directly convert the response to a JS object |
| 23 | +export const weatherData = await fetch( |
| 24 | + `https://api.open-meteo.com/v1/forecast?latitude=${$.lat}&longitude=${$.long}&hourly=temperature_2m,precipitation` |
| 25 | +).then((response) => response.json()); |
| 26 | +``` |
| 27 | + |
| 28 | +As you can see in the response we received an hourly rain and temperature forecast for multiple days. For our first chart I'd like to display the amount of rain in mm per hour for the current day. |
| 29 | +First we need to transform `hourly.time` and `hourly.precipitation` into a single array of objects. We can map and filter the exported `weatherData` and create a new exported variable called `precipitationData`. |
| 30 | + |
| 31 | +```typescript |
| 32 | +export const forecastData = $.weatherData.hourly.precipitation |
| 33 | + // Map to object |
| 34 | + .map((precipitation: number, i: number) => { |
| 35 | + const date = new Date($.weatherData.hourly.time[i]); |
| 36 | + const temperature_2m = $.weatherData.hourly.temperature_2m[i]; |
| 37 | + |
| 38 | + return { |
| 39 | + precipitation, |
| 40 | + date, |
| 41 | + temperature: temperature_2m, |
| 42 | + hour: date.getHours(), |
| 43 | + }; |
| 44 | + }) |
| 45 | + // Filter objects to only get the hours of today |
| 46 | + .filter( |
| 47 | + (data: { precipitation: number; date: Date; hour: number }) => |
| 48 | + data.date.getDate() === new Date().getDate() |
| 49 | + ); |
| 50 | +``` |
| 51 | + |
| 52 | +## First chart with VegaLite |
| 53 | + |
| 54 | +Now that we've got some data we can create our first chart! After importing `react-vega` we can use the newly created `$.forecastData` dataset to show the precipitation forecast per hour in a line chart. I also added an additional line to display the current time in the chart. |
| 55 | + |
| 56 | +```typescript |
| 57 | +import { VegaLite } from "react-vega"; |
| 58 | + |
| 59 | +const spec = { |
| 60 | + $schema: "https://vega.github.io/schema/vega-lite/v5.json", |
| 61 | + autosize: "fit", |
| 62 | + width: "700", |
| 63 | + layer: [ |
| 64 | + // Config for precipitation forecast |
| 65 | + { |
| 66 | + mark: "line", |
| 67 | + data: { values: $.forecastData }, |
| 68 | + encoding: { |
| 69 | + y: { |
| 70 | + field: "precipitation", |
| 71 | + type: "quantitative", |
| 72 | + }, |
| 73 | + x: { |
| 74 | + field: "hour", |
| 75 | + }, |
| 76 | + }, |
| 77 | + }, |
| 78 | + // Config for displaying current time |
| 79 | + { |
| 80 | + data: { values: [{ hour: new Date().getHours() }] }, |
| 81 | + mark: { type: "rule", strokeDash: [2, 2], size: 2, color: "red" }, |
| 82 | + encoding: { |
| 83 | + x: { field: "hour" }, |
| 84 | + }, |
| 85 | + }, |
| 86 | + ], |
| 87 | +}; |
| 88 | + |
| 89 | +export default <VegaLite spec={spec} />; |
| 90 | +``` |
| 91 | + |
| 92 | +Very nice, we managed to create a first chart using real api data! |
| 93 | +But what if we would like to know the forecast of another city? If we update the exported latitude and longitude variables we automatically trigger the previous api call and therefor also rerender the chart. |
| 94 | + |
| 95 | +Try it out! |
| 96 | +<small>(Click the arrow on the side of the chart below to show the code of the dropdown)</small> |
| 97 | + |
| 98 | +```typescript |
| 99 | +// @default-collapsed |
| 100 | +export let select = typecell.Input( |
| 101 | + <select> |
| 102 | + <option value="Amsterdam">Amsterdam</option> |
| 103 | + <option value="London">London</option> |
| 104 | + <option value="Istanbul">Istanbul</option> |
| 105 | + </select>, |
| 106 | + "Amsterdam" |
| 107 | +); |
| 108 | + |
| 109 | +export default ( |
| 110 | + <div> |
| 111 | + <b>Select other city</b> <br></br> |
| 112 | + {select} |
| 113 | + </div> |
| 114 | +); |
| 115 | +``` |
| 116 | + |
| 117 | +```typescript |
| 118 | +switch ($.select[0]) { |
| 119 | + case "Amsterdam": |
| 120 | + $.lat = 52.3738; |
| 121 | + $.long = 4.891; |
| 122 | + return; |
| 123 | + case "London": |
| 124 | + $.lat = 51.509865; |
| 125 | + $.long = -0.118092; |
| 126 | + return; |
| 127 | + case "Istanbul": |
| 128 | + $.lat = 41.047867; |
| 129 | + $.long = 28.898272; |
| 130 | + return; |
| 131 | + default: |
| 132 | + return; |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +## Next up - Chart.js |
| 137 | + |
| 138 | +Notice that we also have temperature data in the `$.forecastData` array. Instead of showing the rain forecast, let's try and make a bar chart with the temperature data per hour. Chart.js works a little different compared to VegaLite. Instead of providing a single data array we need to separate labels and the data values. |
| 139 | + |
| 140 | +```typescript |
| 141 | +import { |
| 142 | + Chart as ChartJS, |
| 143 | + CategoryScale, |
| 144 | + LinearScale, |
| 145 | + BarElement, |
| 146 | + Title, |
| 147 | + Tooltip, |
| 148 | + Legend, |
| 149 | +} from "chart.js"; |
| 150 | +import { Bar } from "react-chartjs-2"; |
| 151 | + |
| 152 | +ChartJS.register( |
| 153 | + CategoryScale, |
| 154 | + LinearScale, |
| 155 | + BarElement, |
| 156 | + Title, |
| 157 | + Tooltip, |
| 158 | + Legend |
| 159 | +); |
| 160 | + |
| 161 | +export const options = { |
| 162 | + responsive: true, |
| 163 | + height: 100, |
| 164 | +}; |
| 165 | + |
| 166 | +// Get the labels by mapping the forecastData object into the hour as a string |
| 167 | +const labels = $.forecastData.map( |
| 168 | + (d: { precipitation: number; hour: number }) => d.hour.toString() |
| 169 | +); |
| 170 | + |
| 171 | +export const data = { |
| 172 | + labels, |
| 173 | + datasets: [ |
| 174 | + { |
| 175 | + label: "Temperature", |
| 176 | + // We do the same here for temperature |
| 177 | + data: $.forecastData.map( |
| 178 | + (d: { temperature: number; hour: number }) => d.temperature |
| 179 | + ), |
| 180 | + backgroundColor: "rgba(43, 99, 132, 0.8)", |
| 181 | + }, |
| 182 | + ], |
| 183 | +}; |
| 184 | + |
| 185 | +export default ( |
| 186 | + <div> |
| 187 | + <Bar options={options} data={data} /> |
| 188 | + </div> |
| 189 | +); |
| 190 | +``` |
0 commit comments