{"id":28179,"date":"2022-03-23T22:25:02","date_gmt":"2022-03-23T22:25:02","guid":{"rendered":"http:\/\/Data-Visualizations-with-D3.js-and-Redwood"},"modified":"2022-03-23T22:25:02","modified_gmt":"2022-03-23T22:25:02","slug":"data-visualizations-with-d3-js-and-redwood","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/","title":{"rendered":"Data Visualizations with D3"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>Showing users data in easy to understand formats is an important part of front-end development these days. You can add images, make updates with CSS, or use a number of different libraries. While these approaches are fine, there is another way to generate custom data-based graphics quickly with <a href=\"https:\/\/d3js.org\/\">D3js<\/a>.<\/p>\n<p>In this tutorial, we\u2019ll create a Redwood app that takes data from the back-end and display it in different charts on the front-end. We\u2019ll also be able to auto-upload images of the charts to Cloudinary so we can show them to people outside of the app.<\/p>\n<h2>Getting everything set up<\/h2>\n<p>There are a few things that we need to have in place before we get started.<\/p>\n<h3>Setting up Cloudinary<\/h3>\n<p>One of the things you\u2019ll need to follow along is a free Cloudinary account. You can sign up for one of those <a href=\"https:\/\/cloudinary.com\/users\/register\/free\">here<\/a>.<\/p>\n<h3>Setting up the Redwood app<\/h3>\n<p>To start, open a terminal and run the following command.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn create redwood-app d3-visuals\n<\/code><\/span><\/pre>\n<p>This will bootstrap a new Redwood app with multiple files and folders that hold the front-end and the back-end code. You\u2019ll find all of the back-end code in the <code>api<\/code> directory and all of the front-end code is in the <code>web<\/code> directory.<\/p>\n<p>We\u2019ll also be working with a Postgres database, so if you don\u2019t have a local instance installed you can <a href=\"https:\/\/www.postgresql.org\/download\/\">download it here<\/a>.<\/p>\n<h2>Building the GraphQL back-end<\/h2>\n<p>We\u2019ll start work on the back-end so that we get the business logic in place.<\/p>\n<h3>Making the models<\/h3>\n<p>Go to the <code>api &gt; db<\/code> directory and open the <code>schema.prisma<\/code> file. This is where we\u2019ll make our connection to the database and define the models for our tables. We\u2019ll start by updating the <code>provider<\/code> to use our Postgres instance.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">datasource db {\n  provider = <span class=\"hljs-string\">\"postgresql\"<\/span>\n  url      = env(<span class=\"hljs-string\">\"DATABASE_URL\"<\/span>)\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>You\u2019ll notice we get the <code>DATABASE_URL<\/code> from the environment variables. So we actually need to make a file that holds this variable. In the root directory of the project, make a new file called <code>.env<\/code>. In this file, we\u2019ll define the connection string for our database. It\u2019ll look similar to this.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">DATABASE_URL=postgres:<span class=\"hljs-comment\">\/\/admin:postgres@localhost:5432\/d3_charts<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Now we can delete the example model in the file and replace it with the tables we need for our data. You can delete the <code>UserExample<\/code> model and replace it with this.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">model Fruit {\n  id    <span class=\"hljs-built_in\">String<\/span> @id @<span class=\"hljs-keyword\">default<\/span>(uuid())\n  label <span class=\"hljs-built_in\">String<\/span>\n  value Int\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This model defines the table for the data we\u2019ll be displaying. You see there\u2019s a mix of text and number values all defined using <a href=\"https:\/\/www.prisma.io\/\">Prisma<\/a>. We default the id to a <code>uuid<\/code> so that we get a unique identifier automatically each time a row is added.<\/p>\n<p>Since we have the model in place, we do need some data to start working with.<\/p>\n<h3>Seeding the data<\/h3>\n<p>We\u2019ll need something to show when we start making the graphics on the front-end, so we\u2019re going to seed our database with some initial values. Go to the <code>seed.js<\/code> file in <code>api &gt; db<\/code> and feel free to delete the commented out code inside the <code>main<\/code> function.<\/p>\n<p>Now we\u2019re going to add the code that will seed each table in the database. All of the following code goes inside the <code>main<\/code> function.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> fruitData = &#91;\n  { <span class=\"hljs-attr\">label<\/span>: <span class=\"hljs-string\">'Tangerine'<\/span>, <span class=\"hljs-attr\">value<\/span>: <span class=\"hljs-number\">10<\/span> },\n  { <span class=\"hljs-attr\">label<\/span>: <span class=\"hljs-string\">'Kumquat'<\/span>, <span class=\"hljs-attr\">value<\/span>: <span class=\"hljs-number\">20<\/span> },\n  { <span class=\"hljs-attr\">label<\/span>: <span class=\"hljs-string\">'Dragonfruit'<\/span>, <span class=\"hljs-attr\">value<\/span>: <span class=\"hljs-number\">14<\/span> },\n  { <span class=\"hljs-attr\">label<\/span>: <span class=\"hljs-string\">'Starfruit'<\/span>, <span class=\"hljs-attr\">value<\/span>: <span class=\"hljs-number\">42<\/span> },\n  { <span class=\"hljs-attr\">label<\/span>: <span class=\"hljs-string\">'Raspberry'<\/span>, <span class=\"hljs-attr\">value<\/span>: <span class=\"hljs-number\">31<\/span> },\n  { <span class=\"hljs-attr\">label<\/span>: <span class=\"hljs-string\">'Plantain'<\/span>, <span class=\"hljs-attr\">value<\/span>: <span class=\"hljs-number\">18<\/span> }\n]\n\n<span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-built_in\">Promise<\/span>.all(\n  fruitData.map(<span class=\"hljs-keyword\">async<\/span> (fruit) =&gt; {\n    <span class=\"hljs-keyword\">const<\/span> record = <span class=\"hljs-keyword\">await<\/span> db.fruit.create({\n      <span class=\"hljs-attr\">data<\/span>: fruit,\n    })\n    <span class=\"hljs-built_in\">console<\/span>.log(record)\n  })\n)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>One thing to note is that the seed data should follow the schema for the tables they will be added to. The data has the same values in the object that are expected in the models we wrote eariler.<\/p>\n<p>Now we can go ahead and run the migration on our database and add the tables and the initial data. To do this, we\u2019ll run 2 Redwood commands.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw prisma migrate dev\nyarn rw prisma db seed\n<\/code><\/span><\/pre>\n<p>The first command connects to the Postgres instance and adds the table schema for the database. The second command adds all of the seed data to the tables in the database.<\/p>\n<p>With the database work out of the way, we\u2019ll be able to move on to the GraphQL server.<\/p>\n<h3>Creating the GraphQL types and resolvers<\/h3>\n<p>Now we have to create the types and resolvers that our front-end will use to communicate with the back-end. For this project, we won\u2019t need to add data through the front-end. We\u2019ll just need to view the data.<\/p>\n<p>There\u2019s a Redwood command that will generate all of the types and resolvers we need to retrieve the data to show in our charts. We\u2019ll need to run this command for each of our tables. Open a terminal and run these 3 commands.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw g sdl fruit\n<\/code><\/span><\/pre>\n<p>These commands generate quite a few new files for us. If you take a look in <code>api &gt; src &gt; graphql<\/code>, you\u2019ll see a new file. The file defines the GraphQL types for the table we created the model for. It has a couple of types for create and update mutations, but we won\u2019t be implementing those.<\/p>\n<p>Now go to <code>api &gt; src &gt; services<\/code>. You\u2019ll see a new folder that has a resolver file and two files related to testing. If you take a look at any of the resolver files, you\u2019ll see that right now we have the queries to read all of the rows in the table.<\/p>\n<p>That\u2019s all we need on the back-end! The database is set up and our GraphQL server is ready to go because Redwood did a lot of heavy lifting here. Now we can shift the focus to the front-end.<\/p>\n<h2>Adding the front-end<\/h2>\n<p>There are a few things we need to do on the front-end to wrap this up. We need to add a chart we build with D3 and we need to save the images to Cloudinary.<\/p>\n<p>We\u2019ll start by adding a few packages to the front-end. In a terminal, go to the <code>web<\/code> directory and run this.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn add d3\n<\/code><\/span><\/pre>\n<p>This gives us the package we need to build the charts. Now we\u2019ll make a new page to show them.<\/p>\n<h3>Making a new page<\/h3>\n<p>We\u2019ll use another Redwood command to create this page for us.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw g page charts \/\n<\/code><\/span><\/pre>\n<p>Take a look in <code>web &gt; src &gt; pages<\/code> and you\u2019ll see a new folder called <code>ChartsPage<\/code>. This folder has the component for the page, a Storybook entry, and a test. Plus the <code>Routes.js<\/code> file has automatically been updated to include this new component as the root page.<\/p>\n<p>If you run the project with <code>yarn rw dev<\/code>, you should see something similar to this.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1631197971\/e-603fc55d218a650069f5228b\/dennkhpqtfswpoxztryo.png\" alt=\"picture of the initial page view\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"1131\"\/><\/p>\n<p>Go to the <code>ChartsPage.js<\/code> file in <code>web &gt; src &gt; pages &gt; ChartsPage<\/code> and delete all of the elements below the <code>&lt;h1&gt;<\/code> and add this.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">PieChart<\/span> <span class=\"hljs-attr\">data<\/span>=<span class=\"hljs-string\">{pieData}<\/span> \/&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This is the component we\u2019re going to make with D3.<\/p>\n<h3>Creating a new chart component with D3<\/h3>\n<p>Next we can make the <code>PieChart<\/code> component. In the terminal, run this command.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw g component PieChart\n<\/code><\/span><\/pre>\n<p>Go to <code>web &gt; src &gt; components &gt; PieChart<\/code> and open the <code>PieChart.js<\/code> file. First let\u2019s add the following imports at the top of the file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> * <span class=\"hljs-keyword\">as<\/span> d3 <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'d3'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useEffect } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Then delete the current component because we\u2019ll replace it with this.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">PieChart<\/span>(<span class=\"hljs-params\">{ data, drawChart }<\/span>) <\/span>{\n  <span class=\"hljs-comment\">\/\/ These are some values that define how the pie chart will be drawn.<\/span>\n  <span class=\"hljs-keyword\">const<\/span> innerRadius = <span class=\"hljs-number\">50<\/span>\n  <span class=\"hljs-keyword\">const<\/span> outerRadius = <span class=\"hljs-number\">150<\/span>\n  <span class=\"hljs-keyword\">const<\/span> margin = {\n    <span class=\"hljs-attr\">top<\/span>: <span class=\"hljs-number\">50<\/span>, <span class=\"hljs-attr\">right<\/span>: <span class=\"hljs-number\">50<\/span>, <span class=\"hljs-attr\">bottom<\/span>: <span class=\"hljs-number\">50<\/span>, <span class=\"hljs-attr\">left<\/span>: <span class=\"hljs-number\">50<\/span>,\n  };\n\n  <span class=\"hljs-keyword\">const<\/span> width = <span class=\"hljs-number\">2<\/span> * outerRadius + margin.left + margin.right;\n  <span class=\"hljs-keyword\">const<\/span> height = <span class=\"hljs-number\">2<\/span> * outerRadius + margin.top + margin.bottom;\n\n  <span class=\"hljs-comment\">\/\/ scaleSequential maps values to an output range based on the<\/span>\n  <span class=\"hljs-comment\">\/\/ interpolator which gives us a color within a certain gradient over the<\/span>\n  <span class=\"hljs-comment\">\/\/ domain which determines how many colors we need.<\/span>\n  <span class=\"hljs-keyword\">const<\/span> colorScale = d3\n    .scaleSequential()\n    .interpolator(d3.interpolateMagma)\n    .domain(&#91;<span class=\"hljs-number\">0<\/span>, data.length]);\n\n  <span class=\"hljs-comment\">\/\/ This is the function we will call to create the chart whenever the data is updated.<\/span>\n  useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    drawChart();\n  }, &#91;data]);\n\n  <span class=\"hljs-comment\">\/\/ This is the function that will actually draw the chart.<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">drawChart<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n    <span class=\"hljs-comment\">\/\/ select the pie-container element and remove the existing svg to draw a fresh one<\/span>\n    d3.select(<span class=\"hljs-string\">'#pie-container'<\/span>)\n      .select(<span class=\"hljs-string\">'svg'<\/span>)\n      .remove();\n\n    <span class=\"hljs-comment\">\/\/ append the new svg inside the pie-container element using the<\/span>\n    <span class=\"hljs-comment\">\/\/ attr values for the width and height we defined earlier then<\/span>\n    <span class=\"hljs-comment\">\/\/ append a new element and use the<\/span>\n    <span class=\"hljs-comment\">\/\/ attr to move it in the view<\/span>\n    <span class=\"hljs-keyword\">const<\/span> svg = d3\n      .select(<span class=\"hljs-string\">'#pie-container'<\/span>)\n      .append(<span class=\"hljs-string\">'svg'<\/span>)\n      .attr(<span class=\"hljs-string\">'width'<\/span>, width)\n      .attr(<span class=\"hljs-string\">'height'<\/span>, height)\n      .append(<span class=\"hljs-string\">'g'<\/span>)\n      .attr(<span class=\"hljs-string\">'transform'<\/span>, <span class=\"hljs-string\">`translate(<span class=\"hljs-subst\">${width <span class=\"hljs-regexp\">\/ 2}, ${height \/<\/span> <span class=\"hljs-number\">2<\/span>}<\/span>)`<\/span>);\n\n    <span class=\"hljs-comment\">\/\/ arc is used to make circular sections and we give it an<\/span>\n    <span class=\"hljs-comment\">\/\/ innerRadius that we define how big the inner circle is and an<\/span>\n    <span class=\"hljs-comment\">\/\/ outerRadius to define how big the outer circle is.<\/span>\n    <span class=\"hljs-keyword\">const<\/span> arcGenerator = d3\n      .arc()\n      .innerRadius(innerRadius)\n      .outerRadius(outerRadius);\n\n    <span class=\"hljs-comment\">\/\/ pie calculates the angles we need to represent the<\/span>\n    <span class=\"hljs-comment\">\/\/ value correctly in the chart<\/span>\n    <span class=\"hljs-keyword\">const<\/span> pieGenerator = d3\n      .pie()\n      .padAngle(<span class=\"hljs-number\">0<\/span>)\n      .value(<span class=\"hljs-function\">(<span class=\"hljs-params\">d<\/span>) =&gt;<\/span> d.value);\n\n    <span class=\"hljs-comment\">\/\/ selectAll gets multiple elements from the document and generates the chart based on the<\/span>\n    <span class=\"hljs-comment\">\/\/ data.<\/span>\n    <span class=\"hljs-keyword\">const<\/span> arc = svg\n      .selectAll()\n      .data(pieGenerator(data))\n      .enter();\n\n    <span class=\"hljs-comment\">\/\/ append arcs for each data segment<\/span>\n    arc\n      .append(<span class=\"hljs-string\">'path'<\/span>)\n      .attr(<span class=\"hljs-string\">'d'<\/span>, arcGenerator)\n      .style(<span class=\"hljs-string\">'fill'<\/span>, (_, i) =&gt; colorScale(i))\n      .style(<span class=\"hljs-string\">'stroke'<\/span>, <span class=\"hljs-string\">'#ffffff'<\/span>)\n      .style(<span class=\"hljs-string\">'stroke-width'<\/span>, <span class=\"hljs-number\">0<\/span>);\n\n    <span class=\"hljs-comment\">\/\/ append text labels for each data segment<\/span>\n    arc\n      .append(<span class=\"hljs-string\">'text'<\/span>)\n      .attr(<span class=\"hljs-string\">'text-anchor'<\/span>, <span class=\"hljs-string\">'middle'<\/span>)\n      .attr(<span class=\"hljs-string\">'alignment-baseline'<\/span>, <span class=\"hljs-string\">'middle'<\/span>)\n      .text(<span class=\"hljs-function\">(<span class=\"hljs-params\">d<\/span>) =&gt;<\/span> d.data.label)\n      .style(<span class=\"hljs-string\">'fill'<\/span>, (_, i) =&gt; colorScale(data.length - i))\n      .attr(<span class=\"hljs-string\">'transform'<\/span>, (d) =&gt; {\n        <span class=\"hljs-keyword\">const<\/span> &#91;x, y] = arcGenerator.centroid(d);\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">`translate(<span class=\"hljs-subst\">${x}<\/span>, <span class=\"hljs-subst\">${y}<\/span>)`<\/span>;\n      });\n  }\n\n  <span class=\"hljs-comment\">\/\/ Return the chart element<\/span>\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"pie-container\"<\/span> \/&gt;<\/span><\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>We\u2019re using a lot of D3 methods chained together and it\u2019ll be easier to understand if you read through the comments in the code snippet.<\/p>\n<p>D3 can be a difficult to get started with and it\u2019s common to need to try things out and refer to <a href=\"https:\/\/d3js.org\/\">the documentation<\/a> often. So if this is still confusing after reading through the comments, take some time to try going through the docs as well.<\/p>\n<p>With the chart component ready, we can pull the data from our GraphQL server.<\/p>\n<h2>Pulling the data into the front-end<\/h2>\n<p>Since we have the back-end ready, all we have to do is write a little query in the <code>ChartsPage.js<\/code> file. So open that file and add the following imports.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { useQuery } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@redwoodjs\/web'<\/span>\n<span class=\"hljs-keyword\">import<\/span> PieChart <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'..\/..\/components\/PieChart\/PieChart'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The first import is how we\u2019re going to connect to the GraphQL back-end and the second lets us use the <code>PieChart<\/code> component. Next we need to add the query for our fruits below these imports.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> GET_FRUITS = gql<span class=\"hljs-string\">`\n  query {\n    fruits {\n      label\n      value\n    }\n  }\n`<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This query will call the resolver to get all of the rows in the fruits table and give us the <code>label<\/code> and <code>value<\/code> for each so we can display them in our chart.<\/p>\n<p>Now we\u2019ll add the call we need to execute this query using the <code>useQuery<\/code> hook we imported. This code will go inside of the <code>ChartsPage<\/code> component.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> { data, loading } = useQuery(GET_FRUITS)\n\n<span class=\"hljs-keyword\">if<\/span> (loading) {\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>Loading...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>We\u2019re getting the data from our query and also a loading state. When you\u2019re working with databases, sometimes you\u2019ll run into latency issues with your requests that could cause a page to crash because it doesn\u2019t have the data it expects. Returning a loading indicator while we wait for the data gives a better user experience.<\/p>\n<p>All that\u2019s left for our component is updating the data value for the <code>PieChart<\/code> that gets rendered. This replaces the previous <code>pieData<\/code> we had in this element a bit earlier, but we still have just one instance of the <code>PieChart<\/code> on the page.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">PieChart<\/span> <span class=\"hljs-attr\">data<\/span>=<span class=\"hljs-string\">{data.fruits}<\/span> \/&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Now if you save everything and run the project with <code>yarn rw dev<\/code>, you\u2019ll see something like this in your browser.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1631197998\/e-603fc55d218a650069f5228b\/jbvyuura74u78dmk5mi3.png\" alt=\"the finished pie chart with data\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1812\" height=\"1258\"\/><\/p>\n<p>All that\u2019s left is uploading a snapshot to Cloudinary!<\/p>\n<h3>Saving the image<\/h3>\n<p>We\u2019ll add a little function inside the <code>PieChart.js<\/code> file right above our component declaration.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> uploadChart = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> svgEl = <span class=\"hljs-built_in\">document<\/span>.getElementsByTagName(<span class=\"hljs-string\">'svg'<\/span>)&#91;<span class=\"hljs-number\">0<\/span>]\n\n  <span class=\"hljs-keyword\">const<\/span> svgString = <span class=\"hljs-keyword\">new<\/span> XMLSerializer().serializeToString(svgEl)\n\n  <span class=\"hljs-keyword\">const<\/span> base64 = <span class=\"hljs-built_in\">window<\/span>.btoa(svgString);\n\n  <span class=\"hljs-keyword\">const<\/span> imgSrc = <span class=\"hljs-string\">`data:image\/svg+xml;base64,<span class=\"hljs-subst\">${base64}<\/span>`<\/span>;\n\n  <span class=\"hljs-keyword\">const<\/span> uploadApi = <span class=\"hljs-string\">'https:\/\/api.cloudinary.com\/v1_1\/your_cloud_name\/upload'<\/span>\n\n  <span class=\"hljs-keyword\">const<\/span> body = {\n    <span class=\"hljs-string\">'file'<\/span>: imgSrc,\n    <span class=\"hljs-string\">'upload_preset'<\/span>: <span class=\"hljs-string\">'your_preset_name'<\/span>\n  }\n\n  fetch(uploadApi, {\n    <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n    <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify(body)\n  })\n  .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">response<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-built_in\">console<\/span>.log(response.text)\n  })\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>First we grab the <code>svg<\/code> element that has the chart image. Then we convert that to a string using the <code>XMLSerializer<\/code>. That\u2019s so we can get a base64 string for the image using the <code>btoa<\/code> function in the browser. The next step appends the image type on the base64 string so that when we upload the image, it\u2019s able to be processed as an SVG image.<\/p>\n<p>Then we get the Cloudinary API to handle uploads and set the values for the post request. You\u2019ll need to go in <a href=\"https:\/\/cloudinary.com\/console\/settings\/upload\">your Cloudinary settings<\/a> and make an upload preset if you don\u2019t have one already.<\/p>\n<p>This function will be added to the <code>useEffect<\/code> hook after we create the chart. That way each time the data updates, we\u2019ll have an updated image being uploaded to Cloudinary.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  drawChart();\n  uploadChart();\n}, &#91;data]);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Now you have an app that can generate report graphics and upload them to another source!<\/p>\n<h2>Finished code<\/h2>\n<p>You can take a look at some of the front-end code in <a href=\"https:\/\/codesandbox.io\/s\/tender-pare-zemsh\">this Code Sandbox<\/a> or you can check out the full project in <a href=\"https:\/\/github.com\/flippedcoder\/blog-examples\/tree\/main\/d3-visuals\">the <code>d3-visuals<\/code> folder of this repo<\/a>.<\/p>\n<\/div>\n  \n  <div class=\"wp-block-cloudinary-code-sandbox \">\n    <iframe\n      src=\"https:\/\/codesandbox.io\/embed\/tender-pare-zemsh?theme=dark&amp;codemirror=1&amp;highlights=&amp;editorsize=50&amp;fontsize=14&amp;expanddevtools=0&amp;hidedevtools=0&amp;eslint=0&amp;forcerefresh=0&amp;hidenavigation=0&amp;initialpath=%2F&amp;module=&amp;moduleview=0&amp;previewwindow=&amp;view=&amp;runonclick=1\"\n      height=\"500\"\n      style=\"width: 100%;\"\n      title=\"tender-pare-zemsh\"\n      loading=\"lazy\"\n      allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n      sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n    ><\/iframe>\n  <\/div>\n\n  <div class=\"wp-block-cloudinary-markdown \"><h2>Conclusion<\/h2>\n<p>You can make charts with D3 now! Getting used to all of the options you have in D3 can take a while, but once you play with it you\u2019ll be able to make graphics that can\u2019t be generated with CSS. You can even make these graphics interactive with some JavaScript work.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":28180,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[134,370,177,246,371],"class_list":["post-28179","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-guest-post","tag-image","tag-javascript","tag-react","tag-under-review"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Data Visualizations with D3<\/title>\n<meta name=\"description\" content=\"Sometimes you need to make custom graphics to show complex data. In this tutorial, you&#039;ll learn how to do that with D3js and then upload snapshots to Cloudinary!\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Data Visualizations with D3\" \/>\n<meta property=\"og:description\" content=\"Sometimes you need to make custom graphics to show complex data. In this tutorial, you&#039;ll learn how to do that with D3js and then upload snapshots to Cloudinary!\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2022-03-23T22:25:02+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925198\/Web_Assets\/blog\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13.jpg?_i=AA\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Data Visualizations with D3\",\"datePublished\":\"2022-03-23T22:25:02+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/\"},\"wordCount\":4,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925198\/Web_Assets\/blog\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13.jpg?_i=AA\",\"keywords\":[\"Guest Post\",\"Image\",\"Javascript\",\"React\",\"Under Review\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2022\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/\",\"name\":\"Data Visualizations with D3\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925198\/Web_Assets\/blog\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13.jpg?_i=AA\",\"datePublished\":\"2022-03-23T22:25:02+00:00\",\"description\":\"Sometimes you need to make custom graphics to show complex data. In this tutorial, you'll learn how to do that with D3js and then upload snapshots to Cloudinary!\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925198\/Web_Assets\/blog\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925198\/Web_Assets\/blog\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13.jpg?_i=AA\",\"width\":4195,\"height\":2802},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Data Visualizations with D3\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Data Visualizations with D3","description":"Sometimes you need to make custom graphics to show complex data. In this tutorial, you'll learn how to do that with D3js and then upload snapshots to Cloudinary!","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/","og_locale":"en_US","og_type":"article","og_title":"Data Visualizations with D3","og_description":"Sometimes you need to make custom graphics to show complex data. In this tutorial, you'll learn how to do that with D3js and then upload snapshots to Cloudinary!","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/","og_site_name":"Cloudinary Blog","article_published_time":"2022-03-23T22:25:02+00:00","twitter_card":"summary_large_image","twitter_image":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925198\/Web_Assets\/blog\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13.jpg?_i=AA","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/"},"author":{"name":"","@id":""},"headline":"Data Visualizations with D3","datePublished":"2022-03-23T22:25:02+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/"},"wordCount":4,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925198\/Web_Assets\/blog\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13.jpg?_i=AA","keywords":["Guest Post","Image","Javascript","React","Under Review"],"inLanguage":"en-US","copyrightYear":"2022","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/","name":"Data Visualizations with D3","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925198\/Web_Assets\/blog\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13.jpg?_i=AA","datePublished":"2022-03-23T22:25:02+00:00","description":"Sometimes you need to make custom graphics to show complex data. In this tutorial, you'll learn how to do that with D3js and then upload snapshots to Cloudinary!","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925198\/Web_Assets\/blog\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925198\/Web_Assets\/blog\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13.jpg?_i=AA","width":4195,"height":2802},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/data-visualizations-with-d3-js-and-redwood\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Data Visualizations with D3"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925198\/Web_Assets\/blog\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13\/5f4c315e2c9b75b5fed7fd55d513f5c1abb18d34-4195x2802-1_2818059b13.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28179","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=28179"}],"version-history":[{"count":0,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28179\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28180"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28179"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28179"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28179"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}