Tutorials on Visualization

Learn about Visualization from fellow newline community members!

  • React
  • Angular
  • Vue
  • Svelte
  • NextJS
  • Redux
  • Apollo
  • Storybook
  • D3
  • Testing Library
  • JavaScript
  • TypeScript
  • Node.js
  • Deno
  • Rust
  • Python
  • GraphQL
  • React
  • Angular
  • Vue
  • Svelte
  • NextJS
  • Redux
  • Apollo
  • Storybook
  • D3
  • Testing Library
  • JavaScript
  • TypeScript
  • Node.js
  • Deno
  • Rust
  • Python
  • GraphQL

Visualizing Geographic SQL Data on Google Maps

Analytics dashboards display different data visualizations to represent and convey data in ways that allow users to quickly digest and analyze information. Most multivariate datasets consumed by dashboards include a spatial field/s, such as an observation's set of coordinates (latitude and longitude). Plotting this data on a map visualization contextualizes the data within a real-world setting and sheds light on spatial patterns that would otherwise be hidden in the data. Particularly, seeing the distribution of your data across an area connects it to geographical features and area-specific data (i.e., neighborhood/community demographics) available from open data portals. The earliest example of this is the 1854 cholera visualization by John Snow , who marked cholera cases on a map of London's Soho and uncovered the source of the cholera outbreak by noticing a cluster of cases around a water pump. This discovery helped to correctly identify cholera as a waterborne disease and not as an airbourne disease. Ultimately, it changed how we think about disease transmission and the impact our surroundings and environment have on our health. If your data consists of spatial field/s, then you too can apply the simple technique of plotting markers on a map to extrapolate valuable insight from your own data. Map visualizations are eye-catching and take on many forms: heatmaps, choropleth maps, flow maps, spider maps, etc. Although colorful and aesthetically pleasing, these visualizations provide intuitive controls for users to navigate through their data with little effort. To create a map visualization, many popular libraries (e.g., Google Maps API and deck.gl ) support drawing shapes, adding markers and overlaying geospatial visualization layers on top of a set of base map tiles. Each layer generates a pre-defined visualization based on a collection of data. It associates each data point with certain attributes (color, size, etc.) and renders them on to a map. By pairing a map visualization library with React.js, developers can build dynamic map visualizations and embed them into an analytics dashboard. If the visualizations' data comes from a PostgreSQL database, then we can make use of PostGIS geospatial functions to help answer interesting questions related to spatial relationships, such as which data points lie within a 1 km. radius of a specific set of coordinates. Below, I'm going to show you how to visualize geographic data queried from a PostgreSQL database on Google Maps. This tutorial will involve React.js and the @react-google-maps/api library, which contains React.js bindings and hooks to the Google Maps API, to create a map visualization that shows the location of data points. To get started, clone the following two repositories: The first repository contains a Create React App with TypeScript client-side application that displays a query builder for composing and sending queries and a table for presenting the fetched data. The second repository contains a multi-container Docker application that consists of an Express.js API, a PostgreSQL database and pgAdmin. The Express.js API connects to the PostgreSQL database, which contains a single table named cp_squirrels seeded with 2018 Central Park Squirrel Census data from the NYC Open Data portal. Each record in this dataset represents a sighting of an eastern gray squirrel in New York City's Central Park in the year 2018. When a request is sent to the API endpoint POST /api/records , the API processes the query attached as the body of the request and constructs a SQL statement from it. The pg client executes the SQL statement against the PostgreSQL database, and the API sends back the result in the response. Once it receives this response, the client renders the data to the table. To run the client-side application, execute the following commands within the root of the project's directory: Inside of your browser, visit this application at http://localhost:3000/ . Before running the server-side application, add a .env.development file with the following environment variables within the root of the project's directory: ( .env.development ) To run the server-side application, execute the following commands within the root of the project's directory: Currently, the client-side application only displays the data within a table. For it to display the data within a map visualization, we will need to install several NPM packages: The Google Maps API requires an API key, which tracks your map usage. It provides a free quota of Google Map queries, but once you exceed the quota, you will be billed for the excessive usage. Without a valid API key, Google Maps fails to load: The process of generating an API key involves a good number of steps, but it should be straight-forward. First, navigate to your Google Cloud dashboard and create a new project. Let's name the project "react-google-maps-sql-viz." Once the project is created, select this project as the current project in the notifications pop-up. This reloads the dashboard with this project now selected as the current project. Now click on the "+ Enable APIs and Services" button. Within the API library page, click on the "Maps JavaScript API" option. Enable the Maps JavaScript API. Once enabled, the dashboard redirects you to the metrics page of the Maps JavaScript API. Click the "Credentials" option in the left sidebar. Within the "Credentials" page, click the "Credentials in APIs & Services" link. Because this is a new project, there should be zero credentials listed. Click the "+ Create Credentials" button, and within the pop-up dropdown, click the "API key" option. This will generate an API key with default settings. Copy the API key to your clipboard and close the modal. Click on the pencil icon to rename the API key and restrict it to our client-side application. Rename API key to "Google Maps API Key - Development." This key will be reserved for local development and usage metrics recorded during local development will be tied to this single key. Under the "Application Restrictions" section, select the "HTTP referrers (web sites)" option. Below, the "Website restrictions" section appears. Click the "Add an Item" button and enter the referrer " http://localhost:3000/* " as a new item. This ensures our API key can only be used by applications running on http://localhost:3000/ . This key will be invalid for other applications. Finally, under the "API Restrictions" -> "Restrict Key" section, select the "Maps JavaScript API" option in the <select /> element for this key to only allow access to the Google Maps API. All other APIs are off limits. After you finish making these changes, press the "Save" button. Note: Press the "Regenerate Key" button if the API key is compromised or accidentally leaked in a public repository, etc. The dashboard redirects you back to the "API & Services" page, which now displays the updated API key information. Also, don't forget to enable billing! Otherwise, the map tiles fail to load: When you create a billing account and link the project to the billing account, you must provide a valid credit/debit card. When running the client-side application in different environments, each environment supplies a different set of environment variables to the application. For example, if you decide to deploy this client-side application live to production, then you would provide a different API key than the one used for local development. The API key used for local development comes with its own set of restrictions, such as only being valid for applications running on http://localhost:3000/ , and collects metrics specific to local development. For local development, let's create a .env file at the root of the client-side application's project directory. For environment variables to be accessible by Create React App, they must be prefixed with REACT_APP . Therefore, let's name the API key's environment variable REACT_APP_GOOGLE_MAPS_API_KEY , and set it to the API key copied to the clipboard. Let's start off by adding a map to our client-side application. First, import the following components and hooks from the @react-google-maps/api library: ( src/App.tsx ) Let's destructure out the API key's environment variable from process.env : ( src/App.tsx ) Establish where the map will center. Because our dataset focuses on squirrels within New York City's Central Park, let's center the map at Central Park. We will be adding a marker labeled "Central Park" at this location. ( src/App.tsx ) Within the <App /> functional component, let's declare a state variable that will hold an instance of our map in-memory. For now, it will be unused. ( src/App.tsx ) Call the useJsApiLoader hook with the API key and an ID that's set as an attribute of the Google Maps API <script /> tag. Once the API has loaded, isLoaded will be set to true , and we can then render the <GoogleMap /> component. ( src/App.tsx ) Currently, TypeScript doesn't know what the type of our environment variable is. TypeScript expects the googleMapsApiKey option to be set to a string, but it has no idea if the REACT_APP_GOOGLE_MAPS_API_KEY environment variable is a string or not. Under the NodeJS namespace, define the type of this environment variable as a string within the ProcessEnv interface. ( src/react-app-env.d.ts ) Beneath the table, render the map. Only render the map once the Google Maps API has finished loading. Pass the following props to the <GoogleMap /> component: Here, we set the center of the map to Central Park and set the zoom level to 14. Within the map, add a marker at Central Park, which will physically mark the center of the map. ( src/App.tsx ) The onLoad function will set the map instance in state while the onUnmount function will wipe the map instance from state. ( src/App.tsx ) Altogether, here's how your src/App.tsx should look after making the above modifications. ( src/App.tsx ) Within your browser, visit the application at http://localhost:3000/ . When the application loads, a map is rendered below the empty table. At the center of this map is marker, and when you hover over this marker, the mouseover text shown will be "Central Park." Suppose we send a query requesting for all squirrel observations that involved a squirrel with gray colored fur. When we display these observations as rows within a table, answering questions like "Which section of Central Park had the most observations of squirrels with gray colored fur?" becomes difficult. However, if we populate the map with markers of these observations, then answering this question becomes easy because we will be able to see where the markers are located and identify clusters of markers. First, let's import the <InfoWindow /> component from the @react-google-maps/api library. Each <Marker /> component will have an InfoWindow, which displays content in a pop-up window (in this case, it acts as a marker's tooltip), and it will only be shown only when the user clicks on a marker. ( src/App.tsx ) Since each observation ("record") will be rendered as a marker within the map, let's add a Record interface that defines the shape of the data representing these observations mapped to <Marker /> components. ( src/App.tsx ) We only want one InfoWindow to be opened at any given time. Therefore, we will need a state variable to store an ID of the currently opened InfoWindow. ( src/App.tsx ) Map each observation to a <Marker /> component. Each <Marker /> component has a corresponding <InfoWindow /> component. When a marker is clicked on by the user, the marker's corresponding InfoWindow appears with information about the color of the squirrel's fur for that single observation. Since every observation has a unique ID, only one InfoWindow will be shown at any given time. ( src/App.tsx ) Altogether, here's how your src/App.tsx should look after making the above modifications. ( src/App.tsx ) Within the query builder, add a new rule by clicking the "+Rule" button. Set this rule's field to "Primary Fur Color" and enter "Gray" into the value editor. Keep the operator as the default "=" sign. When this query is sent to the Express.js API's POST /api/records endpoint, it produces the condition primary_fur_color = 'Gray' for the SQL statement's WHERE clause and will fetch all of the observations involving squirrels with gray-colored fur. Press the "Send Query" button. Due to the high number of records returned by the API in the response, the browser may freeze temporarily to render all the rows in the table and markers in the map. Once the browser finishes rendering these items, notice how there are many markers on the map and no discernable spatial patterns in the observations. Yike! For large datasets, rendering a marker for each individual observation causes massive performance issues. To avoid these issues, let's make several adjustments: Define a limit on the number of rows that can be added to the table. ( src/App.tsx ) Add a state variable to track the number of rows displayed in the table. Initialize it to five rows. ( src/App.tsx ) Anytime new data is fetched from the API as a result of a new query, reset the number of rows displayed in the table back to five rows. ( src/App.tsx ) Using the slice method, we can limit the number of rows displayed in the table. It is increased by five each time the user clicks the "Load 5 More Records" button. This button disappears once all of the rows are displayed. ( src/App.tsx ) To render a heatmap layer, import the <HeatmapLayer /> component and tell the Google Maps API to load the visualization library . For the libraries option to be set to LIBRARIES , TypeScript must be reassured that LIBRARIES will only contain specific library names. Therefore, import the Libraries type from @react-google-maps/api/dist/utils/make-load-script-url and annotate LIBRARIES with this type. ( src/App.tsx ) ( src/App.tsx ) ( src/App.tsx ) ( src/App.tsx ) Pass a list of the observations' coordinate points to the <HeatmapLayer /> component's data prop. ( src/App.tsx ) Altogether, here's how your src/App.tsx should look after making the above modifications. ( src/App.tsx ) Save the changes and re-enter the same query into the query builder. Now the table displays information only the first five observations of the fetched data, and the heatmap visualization clearly distinguishes the areas with no observations and the areas with many observations. Click here for the final version of this project. Click here for the final version of this project styled with Tailwind CSS . Try visualizing the data with other Google Maps layers.

Thumbnail Image of Tutorial Visualizing Geographic SQL Data on Google Maps

Dynamic Visualisation with Angular

When faced with a need to visualise data, many developers' first instinct is to reach for a 3rd-party charting library. There's nothing wrong with this: there are several robust and full-featured libraries out there, and it's usually smart to leverage existing, good quality code. But we shouldn't forget that Angular, coupled with modern CSS features, also provides a great base on which to create our own, bespoke visualisation components. Doing so gives us the ability to tailor them to our exact use-cases, and avoid the inevitable trade-offs and bloat that comes with trying to solve for the general case of visualisation and charting. Also, creating visualisation components is just plain fun. In this article, we'll explore the process of creating a bar chart component for Angular. Even if you've created similar components before, the approach here might surprise you. We won't be rendering to a canvas element, or to dynamic SVG. We won't be using D3, or any dependencies besides those that come with a standard Angular CLI app. We won't be using any listeners or polling to detect resizes and run complex and flaky layout code. Instead, we'll strive to use modern CSS features, in particular grid and custom properties, to build the component's layout in a declarative and automatically responsive way. The finished chart can be viewed in this simple demo , or in a more complex demo . The complete code for the chart and both examples can be found on GitHub The first thing to do is create our component. We can use Angular CLI's schematics to generate one for us: In a theoretical sense, we can consider our component as a function that maps from its inputs to a rendered output (i.e., the chart). So, let's start by identifying what the input data should look like by defining some TypeScript interfaces inside bar-chart.component.ts : Our chart's raw data will be stored in this structure, and supplied to our component using @Input() properties that we'll define in the BarChartComponent class in bar-chart.component.ts : We'll need to define some additional inputs later, for things like the chart title, but these will suffice for now. Next we'll the basic layout structure of our component. We start with a simple mockup of our bar chart component, placed within the context of a skeleton app page: When building components, it is important to think of layout in terms of "inner" — those aspects controlled by the component code itself — and "outer" — those controlled by the surrounding content. In its most basic form, our component is just a box placed within another larger box (the page): Everything that goes on inside the inner, purple box is the responsibility of the component, but it should make few or no assumptions about the size and position of the box within the larger layout. Instead, it should adapt to the size of its container. This will make it more generic, and more easily re-usable in different situations and contexts. By leaving the positioning and sizing of our component up to its consumer, the dimensions of our component's layout box become an input, as important (and unknown) as the data we'll be showing as columns, series and categories. We could read these dimensions explicitly from the DOM and calculate our entire layout in JavaScript, but this is difficult to do in a truly performant and bulletproof way (although CSS Houdini 's Layout API may change this, when it arrives). To achieve a responsive inner layout without having to calculate everything ourselves, we will use CSS Grid layout. The design our grid, we begin by breaking up the mocked-up content into sections: We can see that, in essence, our component consists of five areas: The next step is to break the layout up further, into the rows and columns of our layout grid, which we then define in bar-chart.component.scss . Since we want our component to be responsive. We also need to consider how these will be sized relative to the total available space. CSS grid provides a great deal of scope of how a row or columns can be sized. But at a high–level, there are four strategies: It is this ability to control the sizing strategy of different sections of our component that makes using grid such a compelling alternative over rendering to canvas or SVG. Even though SVG is responsive in the sense of being scalable to any size without losing detail, its positioning system is extremely primitive, making it impossible to create truly responsive visualisations in a declarative way. Now let's break our design down further, into rows and columns: Note that we will be using grid's named-lines capability to identify important offsets in our grid. This is important to make the CSS we write for each section structurally independent of the rest of the grid. In contrast, hardcoding exact row and column offsets makes it much harder to modify the grid structure later, if we want to introduce a new element, such as a row of controls. We have a fixed set of five rows, and they can be defined as follows: Note that only one of our rows, the data row, will be fully flexible. The others will size to fit their content instead. We are also using the minmax(...) function to give the flexible row a minimum and maximum size. In this, case we give a small minimum of 10em , to ensure that the data at least remains visible, while letting the data row grow as large as it can with a 1fr maximum. Defining the columns of our grid is more complicated, as there will be a dynamic number of them. Fortunately CSS grid provides for this situation. Here we're using a repeat(...) function expression to specify the dynamic data columns. Let's look at it isolation: The repeat(...) expression indicates that we would like to repeat the following column definition some number of times. This can be a static number, but in our case the repeat count is a CSS custom property/variable, called --column-count . This variable is the bridge between our component and its stylesheet, and we'll see later how it can be supplied by the JavaScript code. The [col-start] gives each repeated column a name. These names don't have to be unique, as we can specify one by supplying an index with the name, e.g., col-start 1 , col-start 2 , etc. This lets us place content in a particular column without worrying about the rest of the grid's details. Finally, minmax(5em, 1fr) makes each column size between a minimum of 5em and an unbounded maximum, similarly to how we constrained the data row. Because there may be more than one column, any available space will be shared equally between them. With the grid structure of our component defined, we can begin adding the content. We'll progress through each piece of content: We'll start simple, with the chart title. We can add a new input to configure this: And, if it's set, we'll bind it to an element in the component template: And finally we can add some styles for this element: The legend displays a key of the chart series. We already have an input for this, so we now need to render it into our template: Here we see our first use of a CSS custom property, --color , which we're binding to the value of the color field of the series. This lets us pass a value from the JS realm into CSS, where we can use it in place of a static value: Here we've used it as the background color of a pseudo element. This wouldn't be possible with a regular style attribute binding, and we'd have to introduce an additional element into our HTML. This approach keeps our markup simple. Custom properties help us develop a clean abstraction between the component's JS, template, and stylesheet. The template is only responsible for binding a single, domain-specific value, which the stylesheet bind to whichever elements and properties it wants, without the knowledge of the JS or template. Now we come to the heart of our grid, the categories. The final piece of our CSS grid configuration is to supply the --category-count CSS property we used earlier in the grid-template-columns definition. As this is declared on the host element, we can't bind it in the template. Instead, we can use a host-binding in the component class: We also want to calculate and bind another property: ---maxValue . This will let us communicate to CSS Recall that our category data structure looks like this: For each category, we need to render both values array, as coloured bars in the data area, and its label property, in the axis area. We'll begin with a single loop over the categories field: For the category values , we'll render .category div with a nested ngFor to output a .bar for each value: We move the .category div into the correct position by binding its grid-column style to a string of the form col-start {i+1} . This ensures it will be placed into the correct repeated column. For each .bar we won't bind any styles directly. Instead, we'll supply two CSS variables: --value , which contains the raw frequency value from the values array, and --color , which contains the series color from the series field. We'll use both in the component stylesheet: The .category div is a flex container that aligns its items to the end, which is equivalent to aligning their bottom end to the x axis line. Here we're using the --color property as the background color. However, by abstracting it using a property, we could have media queries to change how it is used, such as applying it as an outline when printing, without requiring a change to the template. The --value property is being used in a calc(...) function to determine the height of the bar. By dividing it by a second, --max-value property, then multiplying it by 100%, we can scale the height of the .bar in a declarative way. In order to supply --max-value , we can use setter function on categories to calculate and bind it: When dealing with unknown data, text labels are a real source of pain. Long labels can easily overflow the container. On a bar chart, the best approach for x axis labels is usually to rotate them at a 45-degree angle. This prevents them overflowing into each other, while being more readable and space efficient than 90-degree labels. To rotate the labels, we can use CSS transforms: However, CSS transforms are not taken into account for layout purposes. The y axis area will be sized as if all the labels were unrotated, giving it a single-line height, and meaning these labels will overflow the bottom of the container. To prevent this, we can use padding-bottom and a bit of trigonometry. Because percentage padding is always calculated based on an element's width, we can use it on the .label-text wrapper to make each label's height proportional to its width. And trigonometry tells use that a horizontal line rotated to 45-degrees covers approximately 70% of its length in the vertical direction. Therefore, a padding-bottom: 70% value on each .label-text will ensure the y axis area is always large enough to contain the rotated labels. Since we don't know the range of the data, we face the problem of producing a range and ticks for the y axis. Our options are: In our implementation, we'll attempt the third choice using an algorithm invented by Paul S. Heckbert for generating "nice" axis labels. The approach was first detailed by Heckbert in the book Graphics Gems, and it works based on the observation that power-of-ten multiples of 1, 2 or 5 tend to look best. Check the example project for the complete implementation. But, the end result is a function niceTicks(min, max, tickCount): number[] that we can use in our categories setter: We render the y axis ticks in the template using another *ngFor loop: To correctly layout the ticks along the length of the y-axis container, we'll use a vertical flexbox with space-between justification and a reverse order: However, the intrinsic height of the .y-tick boxes will prevent them being at exactly the right location. We can solve this by setting the .y-tick height to zero, and applying a CSS transform to move the nested .y-tick-value box into place: By avoiding the use of absolute positioning on the ticks, we ensure that the width of the .y-axis container is still calculated based on the width of the labels, and will be sized correctly without any need to perform JS-based measurement of the element sizes. All that remains is to complete the styling of the axis and the ticks using CSS borders and pseudo elements for the tick marks themselves. We now have the essentials of laying out of chart done. However, we're not yet done. There's still a bit of work to tidy up the styling–applying margins, padding and colours to various elements until we have an attractive looking chart. The nature of this work depends upon the particular look you're trying to achieve, but you can examine the example project for a possible example. It is useful to define sizes, such as margins and padding, using em values, so they scale in proportion to the font size. This gives the component consumer an easy way to scale the chart's text and internal sizes using just the font-size CSS property. At this point we have a working bar chart component that responds flexibly both to different data sources, and the size constraints applied to it by its surrounding layout container. If we were to continue to develop it, there's many more features we could add, such as templated columns, better handling of over-sized tick and category labels, or display-mode controls for stacked and scaled bars. The possibilities are endless, although we should always be careful to evaluate the additional complexity each feature introduces into our codebase against the value it provides. Jon Rimmer is a software developer who lives and works in London.

Thumbnail Image of Tutorial Dynamic Visualisation with Angular

I got a job offer, thanks in a big part to your teaching. They sent a test as part of the interview process, and this was a huge help to implement my own Node server.

This has been a really good investment!

Advance your career with newline Pro.

Only $30 per month for unlimited access to over 60+ books, guides and courses!

Learn More