Using Vue.js 3 component dynamically in the D3.js v7 - d3.js

im using the Vue.js 3 and D3.js v7 to making the flowchart.
My problem here is I can't dynamically append the component inside the D3.js.
My component is imported like shown below.
components: {
StoryPanel: defineAsyncComponent(() => import("#/Pages/Story/Partials/StoryPanel"))
},
let nodes = container.selectAll("g")
.data(root.descendants())
.join("g")
.append("foreignObject")
.attr("width", entityWidth - 10)
.attr("height", 150)
nodes.append("xhtml:div")
.attr("class", "border border-black")
.html(function (d) {
// return '<StoryPanel />' tried this but not working
})
.style("border", '1px solid black')
This is the generated html
<foreignObject width="190" height="150" transform="translate(-95,10)">
<div class="border border-black" style="border: 1px solid black;">
<storypanel></storypanel>
</div>
</foreignObject>
[Edit 1]
Tried this Rendering Vue 3 component into HTML string as #MichalLevĂ˝ suggested, but still not working
.html(function (d) {
const tempApp = createApp({
render() {
return h(StoryPanel,{step: d})
}
})
const el = document.createElement('div');
const mountedApp = tempApp.mount(el)
return mountedApp.$el.outerHTML
})
[Edit 2]
I found it works only when using const instead of a Component.vue
const CircleWithTextSvg = {
name: 'CircleWithTextSvg',
props: {
text: {
type: String,
default: "1"
},
fill: {
type: String,
default: "white"
}
},
template: `<div>template</div>`
}
Any help is appreciated. Thank you.

Use createApp
import {createApp} from 'vue';
and use this instead and return the outerHTML
const app = createApp(Component, {prop1: this.prop1})
.mount(document.createElement('div'))
return app.$el.outerHTML

Related

D3, VueJS, axes of chart not rendering

I am trying to add D3 histogram to a VueJS component and I can't get the axes to appear correctly. The only thing I see rendered is the tip of the y-axis.
here is my js for the chart component:
export default {
name: "Histogram",
props: {
},
mounted() {
this.generateBars();
},
data: () => ({
title: 'Bar Chart'
}),
methods: {
generateBars() {
const sample = [
{
language: 'Rust',
value: 78.9,
color: '#000000'
},
...
{
language: 'Clojure',
value: 59.6,
color: '#507dca'
}
];
const margin = 60
const width = 1000 - 2 * margin;
const height = 600 - 2 * margin;
const svg = d3.select("svg");
const chart = svg.append('g')
.attr('transform', `translate(${margin}, ${margin})`);
const yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, 100]);
chart.append('g')
.call(d3.axisLeft(yScale));
const xScale = d3.scaleBand()
.range([0, width])
.domain(this.sample.map((s) => s.language))
.padding(0.2)
chart.append('g')
.attr('transform', `translate(0, ${height})`)
.call(d3.axisBottom(xScale));
}
}
};
My template for the component is simply the chart itself:
<template>
<div id="container" class="svg-container" align="center">
<h3>{{ title }}</h3>
<svg />
</div>
</template>
Here is what I see:
After further examination, I think the footer is covering the bottom of the chart. Not sure why it does not move down to accommodate the additional content.
Looks like you forgot to set width and height for svg (via html or js).
<svg width="1000" height="600"></svg>
And remove "this" in line:
.domain(this.sample.map((s) => s.language))

React-Material UI-D3 Promise.all does not show chart without double click on menu

I am a newbie in React Router and State, i don't know why that my simple D3 Chart not show if i use condition/steps below
Load data on Apps using Promise.all and store in State (the reason i load data in here is because i try to avoid to load data on each chart page)
Call Dashboard page from App with Navigation
First time it will Load the Dashboard page with my BarChart
When i click on next menu to display the BarChart, it does not show
If i click again on the same menu, it will show again my BarChart
Is it because of Async function (Promise.all) ? How to resolve it?
alphabet.csv sample data
letter,frequency
A,0.08167
B,0.01492
C,0.02782
D,0.04253
E,0.12702
import React, { useState, useEffect } from 'react';
import * as d3 from 'd3';
import { Route, BrowserRouter as Router } from 'react-router-dom';
import Dashboard from './components/Dashboard';
import Dashboard1 from './components/Dashboard1';
export default function App() {
const [result, setResult] = useState([])
const mydata = () => {
const data = Promise.all([
d3.csv("alphabet.csv", d3.autoType),
]).then((result) => {
setResult([result]);
})
return data
}
useEffect(() => mydata(), []);
return (
<Router>
<Route path="/" exact render={(props) => <Dashboard {...props} data={result} />} />
<Route path="/Dashboard" render={(props) => <Dashboard {...props} data={result} />} />
<Route path="/Dashboard1" render={(props) => <Dashboard {...props} data={result} />} />
</Router>
)
}
import React from 'react';
import clsx from 'clsx';
import { makeStyles } from '#material-ui/core/styles';
import Container from '#material-ui/core/Container';
import Grid from '#material-ui/core/Grid';
import Paper from '#material-ui/core/Paper';
import Toolbar from '#material-ui/core/Toolbar';
import Navigation from './Navigation';
import BarChart from './BarChart';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
},
title: {
flexGrow: 1,
},
content: {
flexGrow: 1,
height: '100vh',
overflow: 'auto',
},
container: {
paddingLeft: theme.spacing(2),
paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2),
paddingRight: theme.spacing(2),
},
paper: {
padding: theme.spacing(2),
display: 'flex',
overflow: 'auto',
flexDirection: 'column',
},
fixedHeight: {
height: 350,
},
}));
export default function Dashboard(props) {
const classes = useStyles();
const fixedHeightPaper = clsx(classes.paper, classes.fixedHeight);
return (
<div className={classes.root}>
<Navigation />
<main className={classes.content}>
<Toolbar />
<div className={classes.appBarSpacer} />
<Container maxWidth="lg" className={classes.container}>
<Grid container spacing={3}>
<Grid item xs={12} md={8} lg={9}>
<Paper className={fixedHeightPaper}>
Dashboard<BarChart data={props.data} />
</Paper>
</Grid>
</Grid>
</Container>
</main>
</div>
)
}
import React from 'react';
import clsx from 'clsx';
import { Link } from 'react-router-dom';
import { makeStyles } from '#material-ui/core/styles';
import Drawer from '#material-ui/core/Drawer';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import List from '#material-ui/core/List';
import Typography from '#material-ui/core/Typography';
import Divider from '#material-ui/core/Divider';
import IconButton from '#material-ui/core/IconButton';
import ChevronLeftIcon from '#material-ui/icons/ChevronLeft';
import ListItem from '#material-ui/core/ListItem';
import ListItemIcon from '#material-ui/core/ListItemIcon';
import ListItemText from '#material-ui/core/ListItemText';
import DashboardIcon from '#material-ui/icons/Dashboard';
import BarChartIcon from '#material-ui/icons/BarChart';
const drawerWidth = 55;
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
},
toolbar: {
paddingRight: 24, // keep right padding when drawer closed
},
toolbarIcon: {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: '0 8px',
...theme.mixins.toolbar,
},
appBar: {
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
},
menuButton: {
marginRight: 36,
},
menuButtonHidden: {
display: 'none',
},
title: {
flexGrow: 1,
},
drawerPaper: {
position: 'relative',
whiteSpace: 'nowrap',
width: drawerWidth,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawerPaperClose: {
overflowX: 'hidden',
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
width: theme.spacing(7),
[theme.breakpoints.up('sm')]: {
width: theme.spacing(7),
},
},
appBarSpacer: theme.mixins.toolbar,
}));
export default function Navigation() {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="absolute" className={clsx(classes.appBar)}>
<Toolbar className={classes.toolbar}>
<Typography component="h1" variant="h6" color="inherit" noWrap className={classes.title}>
Testing
</Typography>
</Toolbar>
</AppBar>
<Drawer
variant="permanent"
classes={{
paper: clsx(classes.drawerPaper, classes.drawerPaperClose),
}}
>
<div className={classes.toolbarIcon}>
<IconButton>
<ChevronLeftIcon />
</IconButton>
</div>
<Divider />
<List>
<ListItem button>
<ListItemIcon>
<Link to='/Dashboard'><DashboardIcon /></Link>
</ListItemIcon>
<ListItemText primary="Dashboard" />
</ListItem>
<ListItem button>
<ListItemIcon>
<Link to='/Dashboard1'><BarChartIcon /></Link>
</ListItemIcon>
<ListItemText primary="Dashboard1" />
</ListItem>
</List>
</Drawer>
</div >
)
}
import React, { useRef } from 'react';
import * as d3 from 'd3';
export default function BarChart(props) {
const ref = useRef();
const pdata = props.data;
const margin = ({ top: 30, right: 30, bottom: 60, left: 100 });
const width = window.innerWidth - margin.right - margin.left;
const height = window.innerHeight - margin.top - margin.bottom + 100;
const color = "steelblue";
if (pdata.length > 0) {
const data = pdata[0];
const x = d3.scaleBand()
.domain(d3.range(data[0].length))
.range([margin.left, width - margin.right])
.padding(0.1);
const xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).tickFormat(i => data[0][i].letter).tickSizeOuter(0))
.attr('font-size', '18px')
.call(g => g.append("text")
.attr("x", width / 2)
.attr("y", margin.bottom)
.attr("fill", "currentColor")
.text("Letter"));
const y = d3.scaleLinear()
.domain([0, d3.max(data[0], d => d.frequency)]).nice()
.range([height - margin.bottom, margin.top]);
const yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y).ticks(20, data.format))
.attr('font-size', '18px')
.call(g => g.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -margin.left)
.attr("fill", "currentColor")
.text("Frequency"));
const svg = d3.select(ref.current)
.attr("viewBox", [0, 0, width, height]);
svg.append("g")
.call(xAxis);
svg.append("g")
.call(yAxis);
svg.append("g")
.attr("fill", color)
.selectAll("rect")
.data(data[0])
.join("rect")
.attr("x", (d, i) => x(i))
.attr("y", d => y(d.frequency))
.attr("height", d => y(0) - y(d.frequency))
.attr("width", x.bandwidth());
}
return <svg ref={ref} />
}
Not sure if this was answered, but this line
setResult([result]);
could be the culprit. Is the result already an array? If yes, then just do
setResult(result);
because you might not be mapping over the correct data.

How to create d3 svg in vue-cli?

I'm new to d3 and trying to create d3 SVG in vue-cli.
the browser does not show anything with the code as below, but it work on HTML without vue.
Could anyone tell me what's the matter with my coding?
Thank you very much!
<template>
<div>
<div id='chart' ></div>
</div>
</template>
<script>
import * as d3 from 'd3'
export default {
name: 'priceChart',
data() {
return {
}
},
methods:{
createSvg(){
const svg =
d3.select('#chart').append('svg')
.attr('width',500)
.attr('height',400);
svg.append('rect')
.attr('x',0)
.attr('y',0)
.attr('width',500)
.attr('height',400)
.style('fill','red')
}
},
created(){
this.createSvg();
}
}
</script>
DOM element is not available in created methods, you should put it in mounted
In the created hook, you will be able to access reactive data and
events are active. Templates and Virtual DOM have not yet been mounted
or rendered.
<template>
<div>
<div id='chart' ></div>
</div>
</template>
<script>
import * as d3 from 'd3'
export default {
name: 'priceChart',
data() {
return {
}
},
methods:{
createSvg(){
const svg =
d3.select('#chart').append('svg')
.attr('width',500)
.attr('height',400);
svg.append('rect')
.attr('x',0)
.attr('y',0)
.attr('width',500)
.attr('height',400)
.style('fill','red')
}
},
mounted(){
this.createSvg();
}
}
</script>

Change title dynamically in ndv3 pie chart

I am building a pie-chart with nvd3 and cannot figure out how to make the title dynamic or at least to run a callback when a user hovers over a slice of the pie.
This is the relevant part of my code:
nv.addGraph(function () {
let chart : any = nv.models.pieChart()
.x(function (d : any) {
return d.label;
})
.y(function (d : any) {
return d.value;
})
.showLabels(false)
.labelThreshold(.05) //Configure the minimum slice size for labels to show up
.labelType("percent") //Configure what type of data to show in the label. Can be "key", "value" or "percent"
.donut(true) //Turn on Donut mode. Makes pie chart look tasty!
.donutRatio(0.6) //Configure how big you want the donut hole size to be.
.showLegend(false)
.color(function (d : any) {
return d.data.color;
})
.width(300)
.height(300)
.title("Hello");
//.on("mouseover", function(d: any) { console.log(d); });
d3.select("#chart svg")
.datum(exampleData())
.transition().duration(350)
.call(chart);
return chart;
});
The chart works exactly as intended otherwise.
This is a codepen with the chart. For some reason the color does not work but in my own site it works.
You can use dispatch method of NVD3 library for event subscribing and of course, you can use any native d3 methods, for example d3.select. Just add this to your code:
chart.pie.dispatch.on('elementMouseover', function(e) {
d3.select('.nv-pie-title').text(e.label);
});
chart.pie.dispatch.on('elementMouseout', function(e) {
d3.select('.nv-pie-title').text("Hello");
});
Check working demo in the hidden snippet below:
nv.addGraph(function() {
let chart = nv.models.pieChart()
.x(function(d) {
return d.label;
})
.y(function(d) {
return d.value;
})
.showLabels(false)
.labelThreshold(.05) //Configure the minimum slice size for labels to show up
.labelType("percent") //Configure what type of data to show in the label. Can be "key", "value" or "percent"
.donut(true) //Turn on Donut mode. Makes pie chart look tasty!
.donutRatio(0.6) //Configure how big you want the donut hole size to be.
.showLegend(false)
.color(function(d) {
return d.data.color;
})
.width(300)
.height(300)
.title("Hello");
//.on("mouseover", function(d: any) { console.log(d); });
chart.pie.dispatch.on('elementMouseover', function(e) {
d3.select('.nv-pie-title').text(e.label);
});
chart.pie.dispatch.on('elementMouseout', function(e) {
d3.select('.nv-pie-title').text("Hello");
});
d3.select("#chart svg")
.datum(exampleData())
.transition().duration(350)
.call(chart);
return chart;
});
function exampleData() {
return [{
label: "timeout",
value: "14.2",
data: {
"color": "#f00"
}
}, {
label: "uncontacted",
value: "78.8",
data: {
"color": "#999999"
}
}, {
label: "refused",
value: "6.9",
data: {
"color": "#FFFFFF"
}
}];
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.6/nv.d3.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.7.0/nv.d3.min.js"></script>
<div id="chart">
<svg style="height: 300px; margin: -20px 0;"></svg>
</div>

How to correctly reuse D3.js component in Angular 2?

I want to reuse this D3.js component in Angular 2.
#Component({
selector: 'child-cmp',
template: `
<div>
<svg class="chart"></svg>
</div>
`
})
export class ChildCmp {
ngOnInit() {
let chart = d3.select(".chart")
.append("g")
.append("rect")
.attr("width", 50)
.attr("height", 100);
}
}
http://plnkr.co/edit/PnJfFJ7sOZIxehs2LHNh?p=preview
However, you can see in this demo, two rectangles cannot show together well.
How can I correctly reuse these D3.js component? Thanks
I would try something like that:
#Component({
selector: 'child-cmp',
template: `
<div>
<svg class="chart"></svg>
</div>
`
})
export class ChildCmp {
constructor(private eltRef:ElementRef) {}
ngAfterViewInit() {
let chart = d3.select(this.eltRef.nativeElement)
.select('.chart')
.append("g")
.append("rect")
.attr("width", 50)
.attr("height", 100);
}
}
See this plunkr: http://plnkr.co/edit/lqFGoEvnvGw4PvCs8OWg?p=preview.

Resources