How To Build Custom Data Visualizations Using Luzmo Flex<\/h1>\nPaul Scanlon<\/address>\n 2024-09-12T11:00:00+00:00
\n 2024-10-15T23:05:45+00:00
\n <\/header>\n
This article is sponsored by Luzmo<\/b><\/p>\n
In this article, I\u2019ll introduce you to Luzmo Flex<\/a>, a new feature from the Luzmo team who have been working hard making developer tooling to flatten the on-ramp for analytics reporting and data visualization.<\/p>\nWith Luzmo Flex, you can hook up a dataset and create beautifully crafted, fully customizable interactive charts that meet your reporting needs. They easily integrate and interact with other components of your web app, allowing you to move away from a traditional \u201cdashboard\u201d interface and build more bespoke data products.<\/p>\n
While many charting libraries offer similar features, I often found it challenging to get the data into the right shape that the library needed. In this article, I\u2019ll show you how you can build beautiful data visualizations using the Google Analytics API, and you won\u2019t have to spend any time \u201cmassaging\u201d the data!<\/p>\n
What Is Luzmo Flex?<\/h2>\n
Well, it\u2019s two things, really. First of all, Luzmo is a low-code platform for embedded analytics. You can create datasets from just about anything, connect them to APIs like Google Analytics or your PostgreSQL database, or even upload static data in a .csv<\/code> file and start creating data visualizations with drag and drop.<\/p>\nSecondly, Luzmo Flex is their new React component that can be configured to create custom data visualizations. Everything from the way you query your data to the way you display it can be achieved through code using the LuzmoVizItemComponent<\/a>.<\/p>\n\n\n <\/p>\nWhat makes Luzmo Flex unique is that you can reuse the core functionalities of Luzmo\u2019s low-code embedded analytics platform in your custom-coded components.<\/p>\n
<\/a>\n <\/p>\n
\n\n \u201c<\/span><\/div>\n<\/p><\/div>\n<\/blockquote>\nThat means, besides creating ready-to-use datasets, you can set up functions like the following out-of-the-box:<\/p>\n
\n- Multi-tenant analytics<\/strong>: Showing different data or visualizations to different users of your app.<\/li>\n
- Localization<\/strong>: Displaying charts in multiple languages, currencies, and timezones without much custom development.<\/li>\n
- Interactivity<\/strong>: Set up event listeners to create complex interactivity between Luzmo\u2019s viz items and any non-Luzmo components in your app.<\/li>\n<\/ul>\n
What Can You Build With Luzmo Flex?<\/h3>\n
By combining these off-the-shelf functions with flexibility through code, Luzmo Flex makes a great solution for building bespoke data products that go beyond the limits of a traditional dashboard interface. Below are a few examples of what that could look like.<\/p>\n
Report Builder<\/h4>\n
A custom report builder that lets users search and filter a dataset and render it out using a number of different charts.<\/p>\n<\/p>\n <\/p>\n
<\/p>\n
<\/a>\n Luzmo Flex: Report builder<\/a>. (Large preview<\/a>)
\n <\/figcaption><\/figure>\nFilter Panel<\/h4>\n
Enable powerful filtering using HTML Select inputs, which will update each chart shown on the page.<\/p>\n<\/p>\n <\/p>\n
<\/p>\n
<\/a>\n Luzmo Flex:Dashboard filter panel<\/a>. (Large preview<\/a>)
\n <\/figcaption><\/figure>\nWearables Dashboard<\/h4>\n
Or how about a sleep tracker hooked up to your phone to track all those important snoozes?<\/p>\n<\/p>\n <\/p>\n
<\/p>\n
<\/a>\n Luzmo Flex: Wearables Dashboard<\/a>. (Large preview<\/a>)
\n <\/figcaption><\/figure>\nWhen to Consider Luzmo Flex vs Chart Libraries<\/h2>\n
When building data-intensive applications, using something like Recharts<\/a>, a well-known React charting library, you\u2019ll likely need to reformat the data to fit the required shape. For instance, if I request the top 3 page views from the last seven days for my site, paulie.dev<\/a>, I would have to use the Google Analytics API using the following query.<\/p>\n\nimport dotenv from 'dotenv';\nimport { BetaAnalyticsDataClient } from '@google-analytics\/data';\ndotenv.config();\n\nconst credentials = JSON.parse(\n Buffer.from(process.env.GOOGLE_APPLICATION_CREDENTIALS_BASE64, 'base64').toString('utf-8')\n);\n\nconst analyticsDataClient = new BetaAnalyticsDataClient({\n credentials,\n});\n\nconst [{ rows }] = await analyticsDataClient.runReport({\n property: `properties\/${process.env.GA4_PROPERTY_ID}`,\n dateRanges: [\n {\n startDate: '7daysAgo',\n endDate: 'today',\n },\n ],\n dimensions: [\n {\n name: 'fullPageUrl',\n },\n {\n name: 'pageTitle',\n },\n ],\n metrics: [\n {\n name: 'totalUsers',\n },\n ],\n limit: 3,\n metricAggregations: ['MAXIMUM'],\n});\n<\/code><\/pre>\n<\/div>\nThe response would look something like this:<\/p>\n
\n[\n {\n \"dimensionValues\": [\n {\n \"value\": \"www.paulie.dev\/\",\n \"oneValue\": \"value\"\n },\n {\n \"value\": \"Paul Scanlon | Home\",\n \"oneValue\": \"value\"\n }\n ],\n \"metricValues\": [\n {\n \"value\": \"61\",\n \"oneValue\": \"value\"\n }\n ]\n },\n {\n \"dimensionValues\": [\n {\n \"value\": \"www.paulie.dev\/posts\/2023\/11\/a-set-of-sign-in-with-google-buttons-made-with-tailwind\/\",\n \"oneValue\": \"value\"\n },\n {\n \"value\": \"Paul Scanlon | A set of: \"Sign In With Google\" Buttons Made With Tailwind\",\n \"oneValue\": \"value\"\n }\n ],\n \"metricValues\": [\n {\n \"value\": \"41\",\n \"oneValue\": \"value\"\n }\n ]\n },\n {\n \"dimensionValues\": [\n {\n \"value\": \"www.paulie.dev\/posts\/2023\/10\/what-is-a-proxy-redirect\/\",\n \"oneValue\": \"value\"\n },\n {\n \"value\": \"Paul Scanlon | What Is a Proxy Redirect?\",\n \"oneValue\": \"value\"\n }\n ],\n \"metricValues\": [\n {\n \"value\": \"23\",\n \"oneValue\": \"value\"\n }\n ]\n }\n]\n<\/code><\/pre>\n<\/div>\nTo make that data work with Recharts, I\u2019d need to reformat it so it conforms to the following data shape.<\/p>\n
\n[\n {\n \"name\": \"Paul Scanlon | Home\",\n \"value\": 61\n },\n {\n \"name\": \"Paul Scanlon | A set of: \"Sign In With Google\" Buttons Made With Tailwind\",\n \"value\": 41\n },\n {\n \"name\": \"Paul Scanlon | What Is a Proxy Redirect?\",\n \"value\": 23\n }\n]\n<\/code><\/pre>\n<\/div>\nTo accomplish this, I\u2019d need to use an Array.prototype.map()<\/code><\/a> to iterate over each item, destructure the relevant data and return a key-value pair for the name<\/code> and value<\/code> for each.<\/p>\nconst data = response.rows.map((row) => {\n const { dimensionValues, metricValues } = row;\n\n const pageTitle = dimensionValues[1].value;\n const totalUsers = parseInt(metricValues[0].value);\n\n return {\n name: pageTitle,\n value: totalUsers,\n };\n});\n<\/code><\/pre>\nAnd naturally, if you\u2019re reformatting data this way in your application, you\u2019d also want to write unit tests to ensure the data is always formatted correctly to avoid breaking your application\u2026 and all of this before you even get on to creating your charts!<\/p>\n
With Luzmo Flex, all of this goes away, leaving you more time to focus on which data to display and how best to display it.<\/p>\n
The First Steps to Building Bespoke Data Products<\/h2>\n
Typically, when building user interfaces that display data insights, your first job will be to figure out how to query the data source. This can take many forms, from RESTful API requests to direct database queries or sometimes reading from static files. Your next job will be figuring out when and how often these requests need to occur.<\/p>\n
\n- For data that rarely changes<\/strong>: Perhaps a query in the build step will work.<\/li>\n
- For data that changes regularly<\/strong>: A server-side request on page load.<\/li>\n
- For ever-changing data<\/strong>: A client-side request that polls an API on an interval.<\/li>\n<\/ul>\n
Each will likely inform your application\u2019s architecture, and there\u2019s no single solution to this. Your last job, as mentioned, will be wrangling the responses, reformatting the data, and displaying it in the UI.<\/p>\n
Below, I\u2019ll show you how to do this using Luzmo Flex by using a simple example product.<\/p>\n
What We\u2019re Building: Custom Data Visualizations As Code<\/h2>\n
Here\u2019s a screenshot of a simple data product I\u2019ve built that displays three different charts for different reporting dimensions exposed by the Google Analytics API for page views for my site, paulie.dev<\/a>, from the last seven days.<\/p>\nYou can find all the code used in this article on the following link:<\/p>\n
\n- https:\/\/github.com\/luzmo-official\/luzmo-flex-tutorial<\/a><\/li>\n<\/ul>\n
<\/p>\n <\/p>\n
<\/p>\n
<\/a>\n (Large preview<\/a>)
\n <\/figcaption><\/figure>\nGetting Started With Luzmo<\/h2>\n
Before we get going, hop over to Luzmo and sign up for a free trial<\/a>. You might also like to have a read of one of the getting started guides listed below. In this article, I\u2019ll be using the Next.js starter.<\/p>\n\n- Getting started with Luzmo Data Visualization and Next.js<\/a>\n
\n- https:\/\/github.com\/luzmo-official\/luzmo-getting-started-next-js<\/a><\/li>\n<\/ul>\n<\/li>\n
- Getting started with Luzmo Data Visualization and Astro<\/a>\n
\n- https:\/\/github.com\/luzmo-official\/luzmo-getting-started-astro<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n
Creating a Google Analytics Dataset<\/h2>\n
To create data visualization, you\u2019ll first need data! To achieve this using Luzmo, head over to the dashboard, select Datasets from the navigation, and select GA4 Google Analytics<\/strong>. Follow the steps shown in the UI to connect Luzmo with your Google Analytics account.<\/p>\n<\/p>\n <\/p>\n
<\/p>\n
<\/a>\n (Large preview<\/a>)
\n <\/figcaption><\/figure>\nWith the setup complete, you can now select which reporting dimensions to add to your dataset. To follow along with this article, select Custom selection<\/strong>.<\/p>\n<\/p>\n <\/p>\n
<\/p>\n
<\/a>\n (Large preview<\/a>)
\n <\/figcaption><\/figure>\nLastly, select the following using the search input. Device Category<\/strong>, Page Title<\/strong>, Date<\/strong>, and Total users<\/strong>, then click Import<\/strong> when you\u2019re ready.<\/p>\n<\/p>\n <\/p>\n
<\/p>\n
<\/a>\n (Large preview<\/a>)
\n <\/figcaption><\/figure>\nYou now have all the data required to build the Google Analytics dashboard. You can access the dataset ID from the URL address bar in your browser. You\u2019ll need this in a later step.<\/p>\n<\/p>\n <\/p>\n
<\/p>\n
<\/a>\n (Large preview<\/a>)
\n <\/figcaption><\/figure>\nIf you\u2019ve followed along from either of the first two getting started guides, you\u2019ll have your API Key<\/strong>, API Token<\/strong>, App server<\/strong>, and API host<\/strong> environment variables set up and saved in a .env<\/code> file.<\/p>\nInstall Dependencies<\/h3>\n
If you\u2019ve cloned one of the starter repositories, run the following to install the required dependencies.<\/p>\n
npm install\n<\/code><\/pre>\nNext, install the Luzmo React Embed dependency which exports the LuzmoVizItemComponent<\/code>.<\/p>\nnpm install @luzmo\/react-embed@latest\n<\/code><\/pre>\nNow, find page.tsx<\/code> located in the src\/app<\/strong> directory, and add your dataset id<\/code> as shown below.<\/p>\nAdd the access<\/code> object from the destructured response and pass access.datasets[0].id<\/code> onto the LuzmoClientComponent<\/code> component using a prop named datasetId<\/code>.<\/p>\n\n\/\/ src\/app\/page.tsx\n\n\n+ import dynamic from 'next\/dynamic';\n\nimport Luzmo from '@luzmo\/nodejs-sdk';\n- import LuzmoClientComponent from '.\/components\/luzmo-client-component';\n+ const LuzmoClientComponent = dynamic(() => import('.\/components\/luzmo-client-component'), {\n ssr: false,\n});\n\n\nconst client = new Luzmo({\n api_key: process.env.LUZMO_API_KEY!,\n api_token: process.env.LUZMO_API_TOKEN!,\n host: process.env.NEXT_PUBLIC_LUZMO_API_HOST!,\n});\n\nexport default async function Home() {\n const response = await client.create('authorization', {\n type: 'embed',\n username: 'user id',\n name: 'first name last name',\n email: 'name@email.com',\n access: {\n datasets: [\n {\n- id: '<dataset_id>',\n+ id: '42b43db3-24b2-45e7-98c5-3fcdef20b1a3',\n rights: 'use',\n },\n ],\n },\n });\n\n- const { id, token } = response;\n+ const { id, token, access } = response;\n\n- return <LuzmoClientComponent authKey={id} authToken={token} \/>;\n+ return <LuzmoClientComponent authKey={id} authToken={token} datasetId={access.datasets[0].id} \/>;\n}\n<\/code><\/pre>\n<\/div>\nAnd lastly, find luzmo-client-component.tsx<\/code> located in src\/app\/components<\/strong>. This is where you\u2019ll be creating your charts.<\/p>\nBuilding a Donut Chart<\/h2>\n
The first chart you\u2019ll create is a Donut chart that shows the various devices used by visitors to your site.<\/p>\n<\/p>\n <\/p>\n
<\/p>\n
<\/a>\n (Large preview<\/a>)
\n <\/figcaption><\/figure>\nAdd the following code to luzmo-client-component.tsx<\/code> component.<\/p>\n\n\/\/ src\/app\/component\/luzmo-client-component.tsx\n\n'use client';\n\n+ import { LuzmoVizItemComponent } from '@luzmo\/react-embed';\n\ninterface Props {\n authKey: string;\n authToken: string;\n+ datasetId: string;\n}\n\n- export default function LuzmoClientComponent({ authKey, authToken}: Props) {\n+ export default function LuzmoClientComponent({ authKey, authToken, datasetId }: Props) {\n\n+ const date = new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(); \/\/ creates a date 7 days ago\n\n console.log({ authKey, authToken });\n\n return (\n <section>\n+ <div className='w-1\/2 h-80'>\n+ <LuzmoVizItemComponent\n+ appServer={process.env.NEXT_PUBLIC_LUZMO_APP_SERVER}\n+ apiHost={process.env.NEXT_PUBLIC_LUZMO_API_HOST}\n+ authKey={authKey}\n+ authToken={authToken}\n+ type='donut-chart'\n+ options={{\n+ title: {\n+ en: `Devices from last 7 days`,\n+ },\n+ display: {\n+ title: true,\n+ },\n+ mode: 'donut',\n+ legend: {\n+ position: 'bottom',\n+ },\n+ }}\n+ slots={[\n+ {\n+ name: 'measure',\n+ content: [\n+ {\n+ label: {\n+ en: 'Total users',\n+ },\n+ column: '<column id>', \/\/ Total users\n+ set: datasetId,\n+ type: 'numeric',\n+ format: '.4f',\n+ },\n+ ],\n+ },\n+ {\n+ name: 'category',\n+ content: [\n+ {\n+ label: {\n+ en: 'Device category',\n+ },\n+ column: '<column id>', \/\/ Device category\n+ set: datasetId,\n+ type: 'hierarchy',\n+ },\n+ ],\n+ },\n+ ]}\n+ filters={[\n+ {\n+ condition: 'or',\n+ filters: [\n+ {\n+ expression: '? >= ?',\n+ parameters: [\n+ {\n+ column_id: '<column id>', \/\/ Date\n+ dataset_id: datasetId,\n+ },\n+ date,\n+ ],\n+ },\n+ ],\n+ },\n+ ]}\n+ \/>\n+ <div\/>\n <\/section>\n );\n}\n<\/code><\/pre>\n<\/div>\nThere\u2019s quite a lot going on in the above code snippet, and I will explain it all in due course, but first, I\u2019ll need to cover a particularly tricky part of the configuration.<\/p>\n
Column IDs<\/h3>\n
You\u2019ll notice the filters parameters<\/strong>, measure<\/strong>, and category<\/strong> content<\/strong> all require a column id<\/strong>.<\/p>\nIn the filters parameters<\/strong>, the key is named column_id<\/code>, and in the measure<\/strong> and category<\/strong>, the key is named column<\/code>. Both of these are actually the column IDs from the dataset. And here\u2019s how you can find them.<\/p>\nBack in the Luzmo dashboard, click into your dataset and look for the \u201cmore dots\u201d<\/strong> next to each column heading. From the menu, select Copy column id<\/strong>. Add each column ID to the keys in the configuration objects.<\/p>\nIn my example, I\u2019m using the Total users<\/strong> for the measure<\/strong>, the Device category<\/strong> for the category<\/strong>, and the Date<\/strong> for the filter<\/strong>.<\/p>\n<\/p>\n <\/p>\n
<\/p>\n
<\/a>\n (Large preview<\/a>)
\n <\/figcaption><\/figure>\nIf you\u2019ve added the column IDs correctly, you should be able to see a rendered chart on your screen!<\/p>\n
\u2026 and as promised, here\u2019s a breakdown of the configuration.<\/p>\n
Initial Props Donut chart<\/h3>\n
The first part is fairly straightforward. appServer<\/code> and authKey<\/code> are the environment variables you saved to your .env<\/code> file, and authKey<\/code> and authToken<\/code> are destructured from the authorization request and passed into this component via props.<\/p>\nThe type<\/code> prop determines which type of chart to render. In my example, I\u2019m using donut-chart<\/code>, but you could choose from one of the many options available, area-chart<\/code>, bar-chart<\/code>, bubble-chart<\/code>, box-plot<\/code>, and many more. You can see all the available options in the Luzmo documentation<\/a> under Chart docs<\/a>.<\/p>\n<LuzmoVizItemComponent\n appServer={process.env.NEXT_PUBLIC_LUZMO_APP_SERVER}\n apiHost={process.env.NEXT_PUBLIC_LUZMO_API_HOST}\n authKey={authKey}\n authToken={authToken}\n type='donut-chart'\n<\/code><\/pre>\nThe one thing I should point out is my use of Tailwind classes: w-1\/2<\/code> (width: 50%) and h-80<\/code> (height: 20rem). The LuzmoVizItemComponent<\/code> ships with height 100%, so you\u2019ll need to wrap the component with an element that has an actual height, or you won\u2019t be able to see the chart on the page as it could be 100% of the height of an element with no height.<\/p>\nDonut Chart Options<\/h3>\n
The options<\/code> object is where you can customize the appearance of your chart. It accepts many configuration options, among which:<\/p>\n\n- A
title<\/code> for the chart that accepts a locale<\/a> with corresponding text to display.<\/li>\n- A
display title<\/code> value to determine if the title is shown or not.<\/li>\n- A
mode<\/code> to determine if the chart is to be of type donut or pie chart.<\/li>\n- A
legend<\/code> option to determine where the legend can be positioned.<\/li>\n<\/ul>\nAll the available configuration options can be seen in the Donut chart documentation<\/a>.<\/p>\noptions={{\n title: {\n en: `Devices from last 7 days`,\n },\n display: {\n title: true,\n },\n mode: 'donut',\n legend: {\n position: 'bottom',\n },\n}}\n<\/code><\/pre>\nDonut Chart Slots<\/h3>\n
Slots are where you can configure which column from your dataset to use for the category<\/strong> and measure<\/strong>.<\/p>\nSlots can contain multiple measures, useful for displaying two columns of data per chart, but if more than two are used, one will become the measure.<\/p>\n
Each measure contains a content<\/strong> array. The content array, among many other configurations, can include the following:<\/p>\n\n- A
label<\/code> and locale,<\/li>\n- The
column<\/code> id from the dataset<\/code>,<\/li>\n- The
datasetId<\/code>,<\/li>\n- The
type<\/code> of data you\u2019re displaying,<\/li>\n- A
format<\/code> for the data.<\/li>\n<\/ul>\nThe format used here is Python syntax for floating-point numbers<\/a>; it\u2019s similar to JavaScript\u2019s .toFixed()<\/code> method, e.g number.toFixed(4)<\/code>.<\/p>\nThe hierarchy<\/code> type is \u200b\u200bthe Luzmo standard data type. Any text column is considered as an
\n 2024-10-15T23:05:45+00:00
\n <\/header>\n
With Luzmo Flex, you can hook up a dataset and create beautifully crafted, fully customizable interactive charts that meet your reporting needs. They easily integrate and interact with other components of your web app, allowing you to move away from a traditional \u201cdashboard\u201d interface and build more bespoke data products.<\/p>\n
While many charting libraries offer similar features, I often found it challenging to get the data into the right shape that the library needed. In this article, I\u2019ll show you how you can build beautiful data visualizations using the Google Analytics API, and you won\u2019t have to spend any time \u201cmassaging\u201d the data!<\/p>\n
What Is Luzmo Flex?<\/h2>\n
Well, it\u2019s two things, really. First of all, Luzmo is a low-code platform for embedded analytics. You can create datasets from just about anything, connect them to APIs like Google Analytics or your PostgreSQL database, or even upload static data in a Secondly, Luzmo Flex is their new React component that can be configured to create custom data visualizations. Everything from the way you query your data to the way you display it can be achieved through code using the LuzmoVizItemComponent<\/a>.<\/p>\n \n <\/p>\n What makes Luzmo Flex unique is that you can reuse the core functionalities of Luzmo\u2019s low-code embedded analytics platform in your custom-coded components.<\/p>\n <\/a>\n <\/p>\n That means, besides creating ready-to-use datasets, you can set up functions like the following out-of-the-box:<\/p>\n By combining these off-the-shelf functions with flexibility through code, Luzmo Flex makes a great solution for building bespoke data products that go beyond the limits of a traditional dashboard interface. Below are a few examples of what that could look like.<\/p>\n A custom report builder that lets users search and filter a dataset and render it out using a number of different charts.<\/p>\n <\/p>\n <\/a> Enable powerful filtering using HTML Select inputs, which will update each chart shown on the page.<\/p>\n <\/p>\n <\/a> Or how about a sleep tracker hooked up to your phone to track all those important snoozes?<\/p>\n <\/p>\n <\/a> When building data-intensive applications, using something like Recharts<\/a>, a well-known React charting library, you\u2019ll likely need to reformat the data to fit the required shape. For instance, if I request the top 3 page views from the last seven days for my site, paulie.dev<\/a>, I would have to use the Google Analytics API using the following query.<\/p>\n The response would look something like this:<\/p>\n To make that data work with Recharts, I\u2019d need to reformat it so it conforms to the following data shape.<\/p>\n To accomplish this, I\u2019d need to use an And naturally, if you\u2019re reformatting data this way in your application, you\u2019d also want to write unit tests to ensure the data is always formatted correctly to avoid breaking your application\u2026 and all of this before you even get on to creating your charts!<\/p>\n With Luzmo Flex, all of this goes away, leaving you more time to focus on which data to display and how best to display it.<\/p>\n Typically, when building user interfaces that display data insights, your first job will be to figure out how to query the data source. This can take many forms, from RESTful API requests to direct database queries or sometimes reading from static files. Your next job will be figuring out when and how often these requests need to occur.<\/p>\n Each will likely inform your application\u2019s architecture, and there\u2019s no single solution to this. Your last job, as mentioned, will be wrangling the responses, reformatting the data, and displaying it in the UI.<\/p>\n Below, I\u2019ll show you how to do this using Luzmo Flex by using a simple example product.<\/p>\n Here\u2019s a screenshot of a simple data product I\u2019ve built that displays three different charts for different reporting dimensions exposed by the Google Analytics API for page views for my site, paulie.dev<\/a>, from the last seven days.<\/p>\n You can find all the code used in this article on the following link:<\/p>\n <\/p>\n <\/a> Before we get going, hop over to Luzmo and sign up for a free trial<\/a>. You might also like to have a read of one of the getting started guides listed below. In this article, I\u2019ll be using the Next.js starter.<\/p>\n To create data visualization, you\u2019ll first need data! To achieve this using Luzmo, head over to the dashboard, select Datasets from the navigation, and select GA4 Google Analytics<\/strong>. Follow the steps shown in the UI to connect Luzmo with your Google Analytics account.<\/p>\n <\/p>\n <\/a> With the setup complete, you can now select which reporting dimensions to add to your dataset. To follow along with this article, select Custom selection<\/strong>.<\/p>\n <\/p>\n <\/a> Lastly, select the following using the search input. Device Category<\/strong>, Page Title<\/strong>, Date<\/strong>, and Total users<\/strong>, then click Import<\/strong> when you\u2019re ready.<\/p>\n <\/p>\n <\/a> You now have all the data required to build the Google Analytics dashboard. You can access the dataset ID from the URL address bar in your browser. You\u2019ll need this in a later step.<\/p>\n <\/p>\n <\/a> If you\u2019ve followed along from either of the first two getting started guides, you\u2019ll have your API Key<\/strong>, API Token<\/strong>, App server<\/strong>, and API host<\/strong> environment variables set up and saved in a If you\u2019ve cloned one of the starter repositories, run the following to install the required dependencies.<\/p>\n Next, install the Luzmo React Embed dependency which exports the Now, find Add the And lastly, find The first chart you\u2019ll create is a Donut chart that shows the various devices used by visitors to your site.<\/p>\n <\/p>\n <\/a> Add the following code to There\u2019s quite a lot going on in the above code snippet, and I will explain it all in due course, but first, I\u2019ll need to cover a particularly tricky part of the configuration.<\/p>\n You\u2019ll notice the filters parameters<\/strong>, measure<\/strong>, and category<\/strong> content<\/strong> all require a column id<\/strong>.<\/p>\n In the filters parameters<\/strong>, the key is named Back in the Luzmo dashboard, click into your dataset and look for the \u201cmore dots\u201d<\/strong> next to each column heading. From the menu, select Copy column id<\/strong>. Add each column ID to the keys in the configuration objects.<\/p>\n In my example, I\u2019m using the Total users<\/strong> for the measure<\/strong>, the Device category<\/strong> for the category<\/strong>, and the Date<\/strong> for the filter<\/strong>.<\/p>\n <\/p>\n <\/a> If you\u2019ve added the column IDs correctly, you should be able to see a rendered chart on your screen!<\/p>\n \u2026 and as promised, here\u2019s a breakdown of the configuration.<\/p>\n The first part is fairly straightforward. The The one thing I should point out is my use of Tailwind classes: The All the available configuration options can be seen in the Donut chart documentation<\/a>.<\/p>\n Slots are where you can configure which column from your dataset to use for the category<\/strong> and measure<\/strong>.<\/p>\n Slots can contain multiple measures, useful for displaying two columns of data per chart, but if more than two are used, one will become the measure.<\/p>\n Each measure contains a content<\/strong> array. The content array, among many other configurations, can include the following:<\/p>\n The format used here is Python syntax for floating-point numbers<\/a>; it\u2019s similar to JavaScript\u2019s The .csv<\/code> file and start creating data visualizations with drag and drop.<\/p>\n
\n
\n
What Can You Build With Luzmo Flex?<\/h3>\n
Report Builder<\/h4>\n
<\/p>\n
\n <\/figcaption><\/figure>\nFilter Panel<\/h4>\n
<\/p>\n
\n <\/figcaption><\/figure>\nWearables Dashboard<\/h4>\n
<\/p>\n
\n <\/figcaption><\/figure>\nWhen to Consider Luzmo Flex vs Chart Libraries<\/h2>\n
import dotenv from 'dotenv';\nimport { BetaAnalyticsDataClient } from '@google-analytics\/data';\ndotenv.config();\n\nconst credentials = JSON.parse(\n Buffer.from(process.env.GOOGLE_APPLICATION_CREDENTIALS_BASE64, 'base64').toString('utf-8')\n);\n\nconst analyticsDataClient = new BetaAnalyticsDataClient({\n credentials,\n});\n\nconst [{ rows }] = await analyticsDataClient.runReport({\n property: `properties\/${process.env.GA4_PROPERTY_ID}`,\n dateRanges: [\n {\n startDate: '7daysAgo',\n endDate: 'today',\n },\n ],\n dimensions: [\n {\n name: 'fullPageUrl',\n },\n {\n name: 'pageTitle',\n },\n ],\n metrics: [\n {\n name: 'totalUsers',\n },\n ],\n limit: 3,\n metricAggregations: ['MAXIMUM'],\n});\n<\/code><\/pre>\n<\/div>\n
[\n {\n \"dimensionValues\": [\n {\n \"value\": \"www.paulie.dev\/\",\n \"oneValue\": \"value\"\n },\n {\n \"value\": \"Paul Scanlon | Home\",\n \"oneValue\": \"value\"\n }\n ],\n \"metricValues\": [\n {\n \"value\": \"61\",\n \"oneValue\": \"value\"\n }\n ]\n },\n {\n \"dimensionValues\": [\n {\n \"value\": \"www.paulie.dev\/posts\/2023\/11\/a-set-of-sign-in-with-google-buttons-made-with-tailwind\/\",\n \"oneValue\": \"value\"\n },\n {\n \"value\": \"Paul Scanlon | A set of: \"Sign In With Google\" Buttons Made With Tailwind\",\n \"oneValue\": \"value\"\n }\n ],\n \"metricValues\": [\n {\n \"value\": \"41\",\n \"oneValue\": \"value\"\n }\n ]\n },\n {\n \"dimensionValues\": [\n {\n \"value\": \"www.paulie.dev\/posts\/2023\/10\/what-is-a-proxy-redirect\/\",\n \"oneValue\": \"value\"\n },\n {\n \"value\": \"Paul Scanlon | What Is a Proxy Redirect?\",\n \"oneValue\": \"value\"\n }\n ],\n \"metricValues\": [\n {\n \"value\": \"23\",\n \"oneValue\": \"value\"\n }\n ]\n }\n]\n<\/code><\/pre>\n<\/div>\n
[\n {\n \"name\": \"Paul Scanlon | Home\",\n \"value\": 61\n },\n {\n \"name\": \"Paul Scanlon | A set of: \"Sign In With Google\" Buttons Made With Tailwind\",\n \"value\": 41\n },\n {\n \"name\": \"Paul Scanlon | What Is a Proxy Redirect?\",\n \"value\": 23\n }\n]\n<\/code><\/pre>\n<\/div>\n
Array.prototype.map()<\/code><\/a> to iterate over each item, destructure the relevant data and return a key-value pair for the
name<\/code> and
value<\/code> for each.<\/p>\n
const data = response.rows.map((row) => {\n const { dimensionValues, metricValues } = row;\n\n const pageTitle = dimensionValues[1].value;\n const totalUsers = parseInt(metricValues[0].value);\n\n return {\n name: pageTitle,\n value: totalUsers,\n };\n});\n<\/code><\/pre>\n
The First Steps to Building Bespoke Data Products<\/h2>\n
\n
What We\u2019re Building: Custom Data Visualizations As Code<\/h2>\n
\n
<\/p>\n
\n <\/figcaption><\/figure>\nGetting Started With Luzmo<\/h2>\n
\n
\n
\n
Creating a Google Analytics Dataset<\/h2>\n
<\/p>\n
\n <\/figcaption><\/figure>\n<\/p>\n
\n <\/figcaption><\/figure>\n<\/p>\n
\n <\/figcaption><\/figure>\n<\/p>\n
\n <\/figcaption><\/figure>\n.env<\/code> file.<\/p>\n
Install Dependencies<\/h3>\n
npm install\n<\/code><\/pre>\n
LuzmoVizItemComponent<\/code>.<\/p>\n
npm install @luzmo\/react-embed@latest\n<\/code><\/pre>\n
page.tsx<\/code> located in the src\/app<\/strong> directory, and add your dataset
id<\/code> as shown below.<\/p>\n
access<\/code> object from the destructured response and pass
access.datasets[0].id<\/code> onto the
LuzmoClientComponent<\/code> component using a prop named
datasetId<\/code>.<\/p>\n
\/\/ src\/app\/page.tsx\n\n\n+ import dynamic from 'next\/dynamic';\n\nimport Luzmo from '@luzmo\/nodejs-sdk';\n- import LuzmoClientComponent from '.\/components\/luzmo-client-component';\n+ const LuzmoClientComponent = dynamic(() => import('.\/components\/luzmo-client-component'), {\n ssr: false,\n});\n\n\nconst client = new Luzmo({\n api_key: process.env.LUZMO_API_KEY!,\n api_token: process.env.LUZMO_API_TOKEN!,\n host: process.env.NEXT_PUBLIC_LUZMO_API_HOST!,\n});\n\nexport default async function Home() {\n const response = await client.create('authorization', {\n type: 'embed',\n username: 'user id',\n name: 'first name last name',\n email: 'name@email.com',\n access: {\n datasets: [\n {\n- id: '<dataset_id>',\n+ id: '42b43db3-24b2-45e7-98c5-3fcdef20b1a3',\n rights: 'use',\n },\n ],\n },\n });\n\n- const { id, token } = response;\n+ const { id, token, access } = response;\n\n- return <LuzmoClientComponent authKey={id} authToken={token} \/>;\n+ return <LuzmoClientComponent authKey={id} authToken={token} datasetId={access.datasets[0].id} \/>;\n}\n<\/code><\/pre>\n<\/div>\n
luzmo-client-component.tsx<\/code> located in src\/app\/components<\/strong>. This is where you\u2019ll be creating your charts.<\/p>\n
Building a Donut Chart<\/h2>\n
<\/p>\n
\n <\/figcaption><\/figure>\nluzmo-client-component.tsx<\/code> component.<\/p>\n
\/\/ src\/app\/component\/luzmo-client-component.tsx\n\n'use client';\n\n+ import { LuzmoVizItemComponent } from '@luzmo\/react-embed';\n\ninterface Props {\n authKey: string;\n authToken: string;\n+ datasetId: string;\n}\n\n- export default function LuzmoClientComponent({ authKey, authToken}: Props) {\n+ export default function LuzmoClientComponent({ authKey, authToken, datasetId }: Props) {\n\n+ const date = new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(); \/\/ creates a date 7 days ago\n\n console.log({ authKey, authToken });\n\n return (\n <section>\n+ <div className='w-1\/2 h-80'>\n+ <LuzmoVizItemComponent\n+ appServer={process.env.NEXT_PUBLIC_LUZMO_APP_SERVER}\n+ apiHost={process.env.NEXT_PUBLIC_LUZMO_API_HOST}\n+ authKey={authKey}\n+ authToken={authToken}\n+ type='donut-chart'\n+ options={{\n+ title: {\n+ en: `Devices from last 7 days`,\n+ },\n+ display: {\n+ title: true,\n+ },\n+ mode: 'donut',\n+ legend: {\n+ position: 'bottom',\n+ },\n+ }}\n+ slots={[\n+ {\n+ name: 'measure',\n+ content: [\n+ {\n+ label: {\n+ en: 'Total users',\n+ },\n+ column: '<column id>', \/\/ Total users\n+ set: datasetId,\n+ type: 'numeric',\n+ format: '.4f',\n+ },\n+ ],\n+ },\n+ {\n+ name: 'category',\n+ content: [\n+ {\n+ label: {\n+ en: 'Device category',\n+ },\n+ column: '<column id>', \/\/ Device category\n+ set: datasetId,\n+ type: 'hierarchy',\n+ },\n+ ],\n+ },\n+ ]}\n+ filters={[\n+ {\n+ condition: 'or',\n+ filters: [\n+ {\n+ expression: '? >= ?',\n+ parameters: [\n+ {\n+ column_id: '<column id>', \/\/ Date\n+ dataset_id: datasetId,\n+ },\n+ date,\n+ ],\n+ },\n+ ],\n+ },\n+ ]}\n+ \/>\n+ <div\/>\n <\/section>\n );\n}\n<\/code><\/pre>\n<\/div>\n
Column IDs<\/h3>\n
column_id<\/code>, and in the measure<\/strong> and category<\/strong>, the key is named
column<\/code>. Both of these are actually the column IDs from the dataset. And here\u2019s how you can find them.<\/p>\n
<\/p>\n
\n <\/figcaption><\/figure>\nInitial Props Donut chart<\/h3>\n
appServer<\/code> and
authKey<\/code> are the environment variables you saved to your
.env<\/code> file, and
authKey<\/code> and
authToken<\/code> are destructured from the authorization request and passed into this component via props.<\/p>\n
type<\/code> prop determines which type of chart to render. In my example, I\u2019m using
donut-chart<\/code>, but you could choose from one of the many options available,
area-chart<\/code>,
bar-chart<\/code>,
bubble-chart<\/code>,
box-plot<\/code>, and many more. You can see all the available options in the Luzmo documentation<\/a> under Chart docs<\/a>.<\/p>\n
<LuzmoVizItemComponent\n appServer={process.env.NEXT_PUBLIC_LUZMO_APP_SERVER}\n apiHost={process.env.NEXT_PUBLIC_LUZMO_API_HOST}\n authKey={authKey}\n authToken={authToken}\n type='donut-chart'\n<\/code><\/pre>\n
w-1\/2<\/code> (width: 50%) and
h-80<\/code> (height: 20rem). The
LuzmoVizItemComponent<\/code> ships with height 100%, so you\u2019ll need to wrap the component with an element that has an actual height, or you won\u2019t be able to see the chart on the page as it could be 100% of the height of an element with no height.<\/p>\n
Donut Chart Options<\/h3>\n
options<\/code> object is where you can customize the appearance of your chart. It accepts many configuration options, among which:<\/p>\n
\n
title<\/code> for the chart that accepts a locale<\/a> with corresponding text to display.<\/li>\n
display title<\/code> value to determine if the title is shown or not.<\/li>\n
mode<\/code> to determine if the chart is to be of type donut or pie chart.<\/li>\n
legend<\/code> option to determine where the legend can be positioned.<\/li>\n<\/ul>\n
options={{\n title: {\n en: `Devices from last 7 days`,\n },\n display: {\n title: true,\n },\n mode: 'donut',\n legend: {\n position: 'bottom',\n },\n}}\n<\/code><\/pre>\n
Donut Chart Slots<\/h3>\n
\n
label<\/code> and locale,<\/li>\n
column<\/code> id from the
dataset<\/code>,<\/li>\n
datasetId<\/code>,<\/li>\n
type<\/code> of data you\u2019re displaying,<\/li>\n
format<\/code> for the data.<\/li>\n<\/ul>\n
.toFixed()<\/code> method, e.g
number.toFixed(4)<\/code>.<\/p>\n
hierarchy<\/code> type is \u200b\u200bthe Luzmo standard data type. Any text column is considered as an