{"id":11708,"date":"2017-03-02T23:17:54","date_gmt":"2017-03-02T22:17:54","guid":{"rendered":"http:\/\/www.highcharts.com\/blog\/?p=11708"},"modified":"2026-01-12T09:16:08","modified_gmt":"2026-01-12T09:16:08","slug":"replicating-nyt-weather-app","status":"publish","type":"post","link":"https:\/\/www.highcharts.com\/blog\/tutorials\/replicating-nyt-weather-app\/","title":{"rendered":"Replicating NYT weather App"},"content":{"rendered":"<p>Here comes another treatment of the well-known Tufte weather chart. This time we will use it to demonstrate how to use hc_add_series, a new feature of <a href=\"http:\/\/jkunst.com\/highcharter\/\">Highcharter <\/a>package. This feature makes the more readable by allowing you to add the data argument as numeric, data frame, time series (ts, xts, ohlc) and more.<\/p>\n<p>For this demonstration we will try to replicate the design of the New York Time interactive chart: <a href=\"https:\/\/www.nytimes.com\/interactive\/2016\/02\/19\/us\/2015-year-in-weather-temperature-precipitation.html\"><b>How Much Warmer Was Your City in 2015<\/b><\/a>, where you can choose among <b>over 3K cities<\/b>! Let\u2019s start.<\/p>\n<p><a href=\"https:\/\/hc-dev.highcharts.com\/blog\/post\/replicating-nyt-weather-app\/attachment\/00014g-837\/\" rel=\"attachment wp-att-11710\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-11710 aligncenter\" src=\"https:\/\/wp-assets.highcharts.com\/www-highcharts-com\/blog\/wp-content\/uploads\/2017\/03\/02220818\/00014g-837.gif\" alt=\"\" width=\"649\" height=\"551\" \/><\/a><\/p>\n<h2><b>DATA<\/b><\/h2>\n<p>First, let\u2019s download the source data from The New York Times, and the create an extra variable <i>dt <\/i>to store the date time in a numeric format.<\/p>\n<pre>library(printr)\r\nlibrary(tidyverse)\r\nlibrary(highcharter)\r\nlibrary(lubridate)\r\nlibrary(stringr)\r\nlibrary(forcats)\r\n\r\nurl_base &lt;- \"http:\/\/graphics8.nytimes.com\/newsgraphics\/2016\/01\/01\/weather\/assets\"\r\nfile &lt;- \"new-york_ny.csv\" # \"san-francisco_ca.csv\"\r\nurl_file<\/pre>\n<p>&nbsp;<\/p>\n<table class=\"table-responsive\">\n<thead>\n<tr>\n<th>date<\/th>\n<th>month<\/th>\n<th>temp_max<\/th>\n<th>temp_min<\/th>\n<th>temp_rec_max<\/th>\n<th>temp_rec_min<\/th>\n<th>temp_avg_max<\/th>\n<th>temp_avg_min<\/th>\n<th>temp_rec_high<\/th>\n<th>temp_rec_low<\/th>\n<th>precip_value<\/th>\n<th>precip_actual<\/th>\n<th>precip_normal<\/th>\n<th>precip_rec<\/th>\n<th>snow_rec<\/th>\n<th>annual_average_temperature<\/th>\n<th>departure_from_normal<\/th>\n<th>total_precipitation<\/th>\n<th>precipitation_departure_from_normal<\/th>\n<th>dt<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>2015-01-01<\/td>\n<td>1<\/td>\n<td>39<\/td>\n<td>27<\/td>\n<td>62<\/td>\n<td>-4<\/td>\n<td>39<\/td>\n<td>28<\/td>\n<td>NULL<\/td>\n<td>NULL<\/td>\n<td>0.00<\/td>\n<td>5.23<\/td>\n<td>3.65<\/td>\n<td>NULL<\/td>\n<td>NULL<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>1.42e+12<\/td>\n<\/tr>\n<tr>\n<td>2015-01-02<\/td>\n<td>1<\/td>\n<td>42<\/td>\n<td>35<\/td>\n<td>68<\/td>\n<td>2<\/td>\n<td>39<\/td>\n<td>28<\/td>\n<td>NULL<\/td>\n<td>NULL<\/td>\n<td>0.00<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NULL<\/td>\n<td>NULL<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>1.42e+12<\/td>\n<\/tr>\n<tr>\n<td>2015-01-03<\/td>\n<td>1<\/td>\n<td>42<\/td>\n<td>33<\/td>\n<td>64<\/td>\n<td>-4<\/td>\n<td>39<\/td>\n<td>28<\/td>\n<td>NULL<\/td>\n<td>NULL<\/td>\n<td>0.71<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NULL<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>1.42e+12<\/td>\n<\/tr>\n<tr>\n<td>2015-01-04<\/td>\n<td>1<\/td>\n<td>56<\/td>\n<td>41<\/td>\n<td>66<\/td>\n<td>-3<\/td>\n<td>39<\/td>\n<td>27<\/td>\n<td>NULL<\/td>\n<td>NULL<\/td>\n<td>1.01<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NULL<\/td>\n<td>NULL<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>1.42e+12<\/td>\n<\/tr>\n<tr>\n<td>2015-01-05<\/td>\n<td>1<\/td>\n<td>49<\/td>\n<td>21<\/td>\n<td>64<\/td>\n<td>-4<\/td>\n<td>38<\/td>\n<td>27<\/td>\n<td>NULL<\/td>\n<td>NULL<\/td>\n<td>1.01<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NULL<\/td>\n<td>NULL<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>1.42e+12<\/td>\n<\/tr>\n<tr>\n<td>2015-01-06<\/td>\n<td>1<\/td>\n<td>22<\/td>\n<td>19<\/td>\n<td>72<\/td>\n<td>-2<\/td>\n<td>38<\/td>\n<td>27<\/td>\n<td>NULL<\/td>\n<td>NULL<\/td>\n<td>1.06<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NULL<\/td>\n<td>NULL<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>NA<\/td>\n<td>1.42e+12<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>SETUP<\/h2>\n<p>Next,\u00a0we\u2019ll create a chart framework using Highcharts library:<\/p>\n<pre>hc %\r\n  hc_xAxis(type = \"datetime\", showLastLabel = FALSE,\r\n           dateTimeLabelFormats = list(month = \"%B\")) %&gt;% \r\n  hc_tooltip(shared = TRUE, useHTML = TRUE,\r\n             headerFormat = as.character(tags$small(\"{point.x: %b %d}\", tags$br()))) %&gt;% \r\n  hc_plotOptions(series = list(borderWidth = 0, pointWidth = 4)) %&gt;% \r\n  hc_add_theme(hc_theme_smpl())\r\n\r\nhc\r\n<\/pre>\n<p><iframe title=\"Chart with no data to display\" style=\"width: 100%; height: 670px; border: none;\" src=\"https:\/\/www.highcharts.com\/samples\/embed\/highcharts\/blog\/no-data-to-display\" allow=\"fullscreen\"><\/iframe><\/p>\n<p>Move along. Nothing to see here (as the link between the chart framework and the data has yet to be established)!<\/p>\n<h2>TEMPERATURE DATA<\/h2>\n<p>With the raw data loaded and the chart framework set, we are ready to link the data to the chart framework: I select temperature from the raw data; then, I add the it to the Highcharts object using hc_add_series:<\/p>\n<p><b>Select temperature data<\/b><\/p>\n<pre>dtempgather % \r\n  select(dt, starts_with(\"temp\")) %&gt;% \r\n  select(-temp_rec_high, -temp_rec_low) %&gt;% \r\n  rename(temp_actual_max = temp_max,\r\n         temp_actual_min = temp_min) %&gt;% \r\n  gather(key, value, -dt) %&gt;% \r\n  mutate(key = str_replace(key, \"temp_\", \"\")) \r\n\r\ndtempspread % \r\n  separate(key, c(\"serie\", \"type\"), sep = \"_\") %&gt;% \r\n  spread(type, value)\r\n\r\ntemps % \r\n  mutate(serie = factor(serie, levels = c(\"rec\", \"avg\", \"actual\")),\r\n         serie = fct_recode(serie, Record = \"rec\", Normal = \"avg\", Observed = \"actual\"))\r\n\r\nhead(temps)\r\n<\/pre>\n<table>\n<thead>\n<tr>\n<th>dt<\/th>\n<th>serie<\/th>\n<th>max<\/th>\n<th>min<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>1.42e+12<\/td>\n<td>Observed<\/td>\n<td>39<\/td>\n<td>27<\/td>\n<\/tr>\n<tr>\n<td>1.42e+12<\/td>\n<td>Normal<\/td>\n<td>39<\/td>\n<td>28<\/td>\n<\/tr>\n<tr>\n<td>1.42e+12<\/td>\n<td>Record<\/td>\n<td>62<\/td>\n<td>-4<\/td>\n<\/tr>\n<tr>\n<td>1.42e+12<\/td>\n<td>Observed<\/td>\n<td>42<\/td>\n<td>35<\/td>\n<\/tr>\n<tr>\n<td>1.42e+12<\/td>\n<td>Normal<\/td>\n<td>39<\/td>\n<td>28<\/td>\n<\/tr>\n<tr>\n<td>1.42e+12<\/td>\n<td>Record<\/td>\n<td>68<\/td>\n<td>2<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><b>Add temperature data to the chart<\/b><\/p>\n<pre>hc % \r\n  hc_add_series(temps, type = \"columnrange\",\r\n                hcaes(dt, low = min, high = max, group = serie),\r\n                color = c(\"#ECEBE3\", \"#C8B8B9\", \"#A90048\")) \r\n\r\nhc\r\n<\/pre>\n<p>Let\u2019s test the code and check the result:<\/p>\n<p><iframe title=\"Highcharts Column Range\" style=\"width: 100%; height: 670px; border: none;\" src=\"https:\/\/www.highcharts.com\/samples\/embed\/highcharts\/blog\/column-range\" allow=\"fullscreen\"><\/iframe><br \/>\nGreat! The chart looks like the one in the New York Times!<\/p>\n<p>Next, let\u2019s add a few features such as temperature records and monthly precipitation.<\/p>\n<p><b>Temperature records feature<\/b><br \/>\nTo add this feature, I have to filter the days with temperature records using the columnstemp_rec_high and temp_rec_low. Then, I set some options to show the points such as fill color and longer radius:<\/p>\n<pre>records %\r\n  select(dt, temp_rec_high, temp_rec_low) %&gt;% \r\n  filter(temp_rec_high != \"NULL\" | temp_rec_low != \"NULL\") %&gt;% \r\n  dmap_if(is.character, str_extract, \"\\\\d+\") %&gt;% \r\n  dmap_if(is.character, as.numeric) %&gt;% \r\n  gather(type, value, -dt) %&gt;% \r\n  filter(!is.na(value)) %&gt;% \r\n  mutate(type = str_replace(type, \"temp_rec_\", \"\"),\r\n         type = paste(\"This year record\", type))\r\n\r\npointsyles \r\n\r\n<\/pre>\n<table>\n<thead>\n<tr>\n<th>dt<\/th>\n<th>type<\/th>\n<th>value<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>1.44e+12<\/td>\n<td>This year record high<\/td>\n<td>95<\/td>\n<\/tr>\n<tr>\n<td>1.44e+12<\/td>\n<td>This year record high<\/td>\n<td>97<\/td>\n<\/tr>\n<tr>\n<td>1.45e+12<\/td>\n<td>This year record high<\/td>\n<td>74<\/td>\n<\/tr>\n<tr>\n<td>1.45e+12<\/td>\n<td>This year record high<\/td>\n<td>67<\/td>\n<\/tr>\n<tr>\n<td>1.45e+12<\/td>\n<td>This year record high<\/td>\n<td>67<\/td>\n<\/tr>\n<tr>\n<td>1.45e+12<\/td>\n<td>This year record high<\/td>\n<td>68<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<pre>hc % \r\n  hc_add_series(records, \"point\", hcaes(x = dt, y = value, group = type),\r\n                marker = pointsyles)\r\n\r\nhc\r\n<\/pre>\n<p><iframe title=\"Highcharts Column Range with labels\" style=\"width: 100%; height: 670px; border: none;\" src=\"https:\/\/www.highcharts.com\/samples\/embed\/highcharts\/blog\/column-range-with-labels\" allow=\"fullscreen\"><\/iframe><\/p>\n<h2>Monthly precipitation feature<\/h2>\n<p>This feature is a little bit tricky to set as the t data is on another axis, yet I would like to synchronize it with the main chart. To achieve that, I create a list with two axes using the create_yaxis helper, then I add those axes to the chart:<\/p>\n<pre>axis<\/pre>\n<p>I also hardcode the titles (I know this can be more elegant) and options.<\/p>\n<pre>axis[[1]]$title<\/pre>\n<p><iframe title=\"Highcharts Column Range with axis titles\" style=\"width: 100%; height: 670px; border: none;\" src=\"https:\/\/www.highcharts.com\/samples\/embed\/highcharts\/blog\/column-range-with-axis-titles\" allow=\"fullscreen\"><\/iframe><\/p>\n<p>Now, the two axes are ready and I need to supply data. I add 12 series; one for each month. But we want to associate one legend for all these 12 series. For this I use the id and linkedTo parameters. The id is a &#8216;p&#8217; for the first element and NA to the other 11 elements, and here how to link those 11 elements to the first series (id = &#8216;p&#8217;).<\/p>\n<pre>precip %\r\n  hc_add_series(precip, type = \"area\", hcaes(dt, precip_value, group = month),\r\n                name = \"Precipitation\", color = \"#008ED0\", lineWidth = 1,\r\n                yAxis = 1, fillColor = \"#EBEAE2\", \r\n                id = c(\"p\", rep(NA, 11)), linkedTo = c(NA, rep(\"p\", 11)))\r\n<\/pre>\n<p>I use the same logic above to add normal precipitations by month.<\/p>\n<pre>precipnormal % \r\n  select(dt, precip_normal, month) %&gt;% \r\n  group_by(month) %&gt;% \r\n  filter(row_number() %in% c(1, n())) %&gt;%\r\n  ungroup() %&gt;% \r\n  fill(precip_normal)\r\n\r\nhc % \r\n  hc_add_series(precipnormal, \"line\", hcaes(x = dt, y = precip_normal, group = month),\r\n                name = \"Normal Precipitation\", color = \"#008ED0\", yAxis = 1,\r\n                id = c(\"np\", rep(NA, 11)), linkedTo = c(NA, rep(\"np\", 11)),\r\n<\/pre>\n<p>Are you curious about what this chart looks like now?<\/p>\n<p>(drumroll!)<\/p>\n<p>Voila!<\/p>\n<pre>hc\r\n<\/pre>\n<p><iframe title=\"Highcharts Column Range with precipitation.\" style=\"width: 100%; height: 670px; border: none;\" src=\"https:\/\/www.highcharts.com\/samples\/embed\/highcharts\/blog\/column-range-precipitation\" allow=\"fullscreen\"><\/iframe><br \/>\nWith R you can create a newspaper-style chart with a little data-wrangling and charting. With a little extra love, one can make the code reusable and make shiny app!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A step by step tutorial to replicate the New York Time interactive chart: How Much Warmer Was Your City in 2015.<\/p>\n","protected":false},"author":42,"featured_media":11861,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"meta_title":"","meta_description":"","hc_selected_options":[],"footnotes":""},"categories":[210],"tags":[1094,793],"coauthors":[714],"class_list":["post-11708","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials","tag-highcharts-core","tag-r"],"_links":{"self":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/11708","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/users\/42"}],"replies":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/comments?post=11708"}],"version-history":[{"count":1,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/11708\/revisions"}],"predecessor-version":[{"id":29091,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/11708\/revisions\/29091"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/media\/11861"}],"wp:attachment":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/media?parent=11708"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/categories?post=11708"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/tags?post=11708"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/coauthors?post=11708"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}