## DEV Community

AnupJoseph for Learners Just that

Posted on • Updated on

# Make a scatter plot with Svelte and D3

This blog is second in a series of (unofficial) course notes for the Data Visualization with React and D3 series by Curran Kelleher. Read the introductory blog post here.

The next chart in the series is a scatter plot based on the Iris Flowers Dataset. I figured we could reuse a lot of the code from earlier examples than rewiriting everything from scratch. A properly cleaned version of the dataset by Curran is available here.The dataset has four numerical columns namely `sepal_length,sepal_width,petal_length,petal_width` which we need to convert to numbers. Let's change the `row` and `onMount` functions to reflect this:

``````const  row  =  function  (data)  {
data.sepal_length  =  +data.sepal_length;
data.sepal_width  =  +data.sepal_width;
data.petal_length  =  +data.petal_length;
data.petal_width  =  +data.petal_width;

return data;
};

onMount(async  ()  => {

dataset  =  await  csv(
"https://gist.githubusercontent.com/curran/9e04ccfebeb84bcdc76c/raw/3d0667367fce04e8ca204117c290c42cece7fde0/iris.csv",
row
).then((data)  => {
return  data;
});
});
``````

in case you are wondering where this code came from, look up this gist

The `scaleBand` logic we used before doesn't make much sense in a scatter plot so we need to change that to `scaleLinear`. I am going to plot `petal_width` on X-axis and `petal_length` on Y-axis and so let's change the domain of `xScale` and `yScale` respectively. Again doesn't matter too much, so feel free to change the X and Y axes to your liking

``````\$: xScale  =  scaleLinear()
.domain(extent(dataset, (d)  =>  d.petal_width))
.range([0, width]);

\$: yScale  =  scaleLinear()
.domain(extent(dataset, (d)  =>  d.petal_length))
.range([0, height]);
``````

To make the dots for the scatter plot we can make use of the `<circle>` SVG tag. in the plotting logic let's replace the `<rect>` tag by `circle` and specify its attributes appropriately.

``````<circle
cx={xScale(data.petal_width)}
cy={yScale(data.petal_length)}
r="5"
/>
``````

Something you probably noticed here is that some dots appear to be cutoff from the SVG. The solution I can think of is to shift all the circles to the left. So I am going to wrap all the circles in a `<g>` apply the `transform` directive on it. Lets use the margins that we initialized way back before to translate it across:

``````<g  transform={`translate(\${margin.left},\${margin.right})`}>
{#each  dataset  as data, i}
<circle
cx={xScale(data.petal_width)}
cy={yScale(data.petal_length)}
r="5"
/>
{/each}
</g>
``````

I am also going to reconfigure the scales so that we have more space to work with at the bottom of the page and left.

``````const  innerHeight  =  height  -  margin.top  -  margin.bottom,
innerWidth  =  width  -  margin.left  -  margin.right;

\$: xScale  =  scaleLinear()
.domain(extent(dataset, (d)  =>  d.petal_width))
.range([0, innerWidth]);

\$: yScale  =  scaleLinear()
.domain(extent(dataset, (d)  =>  d.petal_length))
.range([0, innerHeight]);
``````

The Iris flowers in this dataset are of three different species. I think it makes sense to represent them with different colors. I am going to map an array of colors to the species using the `scaleOrdinal` function in D3.

``````const classSet = new Set(dataset.map((d) => d.class));
\$: colorScale = scaleOrdinal()
.domain(classSet)
.range(["#003049", "#d62828", "#f77f00"]);
``````

And then change the `<circle>` element as follows:

``````<circle
cx={xScale(data.petal_width)}
cy={yScale(data.petal_length)}
r="5"
style={`fill:\${colorScale(data.class)}`}
/>
``````

I think I'll make this a (slightly) more fully fleshed out chart and add labels and axes. First lets add x and y-axis labels. Labels are ofcourse just `<text>` elements.
We add the Y-axis label as follows:

``````<text  transform={`translate(\${-25},\${innerHeight  /  2}) rotate(-90)`}
>Petal Length</text>
``````

That cryptic transform essentially just shifts to the left of all the circles and then rotate it. The Y-axis label is added as follows:

``````<text  x={innerWidth  /  2  }  y={innerHeight  +  30}>Petal Width</text>
``````

Let's add an X-axis and Y-axis. We could write our own Axis component but I saw a nice reusable axis component that I quite liked here. I am going to make a few changes there and use it.

``````<script>

import { select, selectAll } from  "d3-selection";
import { axisBottom, axisLeft } from  "d3-axis";

export let  innerHeight;
export let  margin;
export let  position;
export let  scale;

let  transform;
let  g;

\$: {

select(g).selectAll("*").remove();

let  axis;
switch (position) {
case  "bottom":
axis  =  axisBottom(scale).tickSizeOuter(0);
transform  =  `translate(0, \${innerHeight})`;
break;

case  "left":

axis  =  axisLeft(scale).tickSizeOuter(0);
transform  =  `translate(\${margin}, 0)`;
}
select(g).call(axis);
}
</script>

<g  class="axis"  bind:this={g}  {transform} />
``````

Finally lets import the axis component and add it in the `<g>` element like so:

``````<Axis  {innerHeight}  {margin}  scale={xScale}  position="bottom" />
<Axis  {innerHeight}  {margin}  scale={yScale}  position="left" />
``````

Yeah the Y-axis is inverted ðŸ˜¬. Turns out I have doing this a bit wrong. For the record, I did wonder how such thin petals were so long. But then again what do I know about Iris flowers. Fixing this is easy enough. Let's change `yScale` as follows:

``````\$: yScale  =  scaleLinear()
.domain(extent(dataset, (d)  =>  d.petal_length))
.range([innerHeight, 0]);
``````

If you want a simple scatter plot then this is probably all you need. I actually went on to add some more (completely unecessary) styling to it. I wanted to see if for each species of the flower we could have different shaped-petals. Not Iris petals shapes of course but petals nonetheless.

So I gathered some petal shapes from a FrontendMasters workshop by Shirley Wu here, modified them ever so slightly and saved as `paths.js`

``````export const  petalPaths  =  [
'M0 0 C5 5 5 10 0 10 C-5 10 -5 5 0 0',
'M-3.5 0 C-2.5 2.5 2.5 2.5 3.5 0 C5 2.5 2.5 7.5 0 10 C-2.5 7.5 -5.0 2.5 -3.5 0',
'M0 0 C5 2.5 5 7.5 0 10 C-5 7.5 -5 2.5 0 0'
]
``````

Let's import the `petalpaths` and map them to species using D3 `scaleOrdinal`.

``````import { petalPaths } from  "./paths";
\$: shapeScale  =  scaleOrdinal().domain(classSet).range(petalPaths);
``````

Finally instead of plotting circles, we plot a `<path>` element and set the d attribute to `shapeScale`.

``````<path
d={shapeScale(data.class)}
fill={`\${colorScale(data.class)}`}
/>
``````

We wrap it in a `<g>` element and translate it to their respective position so that they dont overlap each other.

``````<g

transform={`translate(\${xScale(data.petal_width)},\${
yScale(data.petal_length)  -  5
})`}
>
<path
d={shapeScale(data.class)}
fill={`\${colorScale(data.class)}`}
/>
</g>
``````

I love this plot!
One thing to notice here however is that the plot does loose some accuracy on adding the shapes. So if that's an important concern then best stay away from it. Anyway, I think I'll end here.
Here's the full code -

So that's it for today. Have a nice day!