I've been trying to put the most basic D3 example into a Svelte app and can't get it to work. At first I tried installing D3 as a node module: npm install d3
but this produces the same result (a lack of result) as importing D3 as an external script from CDN inside of index.html
: <script src="https://d3js.org/d3.v5.min.js"></script>
. Using either method I get a bunch of circular dependency warnings on app start:
(!) Circular dependency: node_modules\d3-selection\src\selection\index.js -> node_modules\d3-selection\src\selection\select.js -> node_modules\d3-selection\src\selection\index.js
But the app starts with no errors, and no D3 dynamic formatting occurs, nor any errors pop up in the DevTools console inside Chrome.
The Svelte component looks like this:
<script>
import * as d3 from 'd3';
var data = [30, 86, 168, 281, 303, 365];
d3.select(".chart")
.selectAll("div")
.data(data)
.enter()
.append("div")
.style("width", function(d) {
return d + "px";
})
.text(function(d) {
return d;
});
</script>
<style>
.chart div {
font: 10px sans-serif;
background-color: steelblue;
text-align: right;
padding: 3px;
margin: 1px;
color: white;
}
</style>
<div class="chart"></div>
Putting the code above into a static HTML file produces a bar chart, as expected. But when run as a Svelte component nothing is displayed.
My rollup.config.js is:
import svelte from 'rollup-plugin-svelte';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
const production = !process.env.ROLLUP_WATCH;
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/bundle.js',
globals: { 'd3': 'd3' },
external: [ 'd3' ]
},
plugins: [
svelte({
dev: !production,
css: css => { css.write('public/bundle.css'); }
}),
resolve({ browser: true }),
commonjs(),
!production && livereload('public'),
production && terser()
],
watch: {
clearScreen: false
}
};
...and index.html is:
<!doctype html>
<html>
<head>
<meta charset='utf8'>
<meta name='viewport' content='width=device-width'>
<title>Svelte app</title>
<link rel='icon' type='image/png' href='favicon.png'>
<link rel='stylesheet' href='global.css'>
<link rel='stylesheet' href='bundle.css'>
</head>
<body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src='bundle.js'></script>
</body>
</html>
I would suspect Rollup not bundling D3 module correctly, but as an external script in <body>
it should in theory work, but it doesn't. Please point me in the right direction, I've spent way too much time trying to get it to work, and as a JS noob am out of options. Thanks!
The <div class="chart"></div>
element doesn't exist when your code first runs — the contents of <script>
run when the component is instantiated. If you need to access DOM elements inside the component, it will first be available inside onMount:
<script>
import { onMount } from 'svelte';
// other code...
onMount(() => {
d3.select('.chart')
// ...
});
</script>
Using a selector like .chart
is dangerous though, because if you had more than one component on the page D3 would select the wrong thing. It's better to use bind:this instead:
<script>
import { onMount } from 'svelte';
// other code...
let el;
onMount(() => {
d3.select(el) // no danger of selecting the wrong element
// ...
});
</script>
<div class="chart" bind:this={el}></div>
Now all you need to change is the CSS — because Svelte will discard selectors that it thinks are unused, and because it can't know what D3 is going to do, it will remove .chart div {...}
. Instead, use the :global(...) modifier to target divs inside your top-level element:
<style>
.chart :global(div) {
/* styles */
}
</style>
With those changes, it works perfectly:
https://svelte.dev/repl/8722c32f4e1a44a98e3a3fc8a095b2d7?version=3.5.3
But in this case, D3 isn't really bringing anything to the party. You could achieve the same result much more simply:
<script>
var data = [30, 86, 168, 281, 303, 365];
</script>
<style>
.chart div {
font: 10px sans-serif;
background-color: steelblue;
text-align: right;
padding: 3px;
margin: 1px;
color: white;
}
</style>
<div class="chart">
{#each data as d}
<div style="width: {d}px">
{d}
</div>
{/each}
</div>
As well as being less code for you to write, your app now contains a lot less JavaScript. Demo here: https://svelte.dev/repl/be5cac1695554b8e9ee6d0bc14b9dff1?version=3.5.3
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With