How to structure Vue for Interactive D3 Chart - javascript

In this Vue component, am trying to create an interactive bar chart hover is seems to be recreating the group element every time the data is updated. If someone can tell me where the problem is because am stuck since I've tried both the general update pattern as well as nest updated pattern.
export default {
name: "StatisticsUI",
props:["reps"],
mounted(){
this.setOptions(),
this.genChart()
},
updated(){
this.genChart()
},
methods:{
......
genChart(){
const data = [
["CCP",2],
["ZPA",1],
["ERA",3],
["POS",4],
]
const svg = d3.select("svg")
const width = svg.attr("width")
const height = svg.attr("height")
const margin ={left: 50, right:50, top:50, bottom:50}
const innerWidth = width - margin.left - margin.right
const innerHeight = height - margin.top - margin.bottom
const g = svg.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
const yAxis = g.append("g").attr("class", "y-axis")
const xAxis = g.append("g").attr("class", "x-axis")
const xValue = d => d[0]
const yValue = d => d[1]
const xScale = d3.scaleBand().domain(data.map(xValue)).range([0, innerWidth]).padding(0.2);
const yScale = d3.scaleLinear().domain([0, d3.max(data, yValue)]).range([innerHeight, 0]);
yAxis.call(d3.axisLeft(yScale));
xAxis
.call(d3.axisBottom(xScale))
.attr("transform", `translate(0, ${innerHeight})`)
let rect = g.selectAll("rect").data(data);
rect.exit().remove()
rect
.enter()
.append("rect")
.merge(rect)
.attr("fill", "#69b3a2")
.attr("x", (d) => xScale(xValue(d)))
.attr("width", xScale.bandwidth())
.attr("height", 0)
.attr("y", innerHeight)
.transition()
.duration(1000)
.delay((d, i) => i * 50)
.ease(d3.easeBounce)
.attr("y", (d) => yScale(yValue(d)))
.attr("height", function (d) {
return innerHeight - yScale(yValue(d));
});
}
}
}

Related

Interactive bar chart domain issue

I'm trying to create an interactive bar chart of the top Forbes 100 companies, with buttons to change between sales and profit.
The first issue I'm having is with the domain:
x.domain([0, d3.max(data, d => d[xValue])])
Error says "data not defined"
but I defined it here:
d3.csv("data/data_clean.csv").then(data => {
data.forEach(d => {
d.sales_usd_billion = Number(d.sales_usd_billion)
d.profit_usd_billion = Number(d.profit_usd_billion)
})
data snapshot:
rank,company,country,sales_usd_billion,sales_unit,profit_usd_billion,profit_unit,assets_usd_billion,market_usd_billion,sales_usd,profit_usd,assets_usd
1,Berkshire Hathaway,United States,276.09,B,89.8,B,958.78,741.48,276.09,89.8,958.78
2,ICBC,China,208.13,B,54.03,B,5518.51,214.43,208.13,54.03,5518.51
3,Saudi Arabian Oil Company (Saudi Aramco),Saudi Arabia,400.38,B,105.36,B,576.04,2292.08,400.38,105.36,576.04
4,JPMorgan Chase,United States,124.54,B,42.12,B,3954.69,374.45,124.54,42.12,3954.69
5,China Construction Bank,China,202.07,B,46.89,B,4746.95,181.32,202.07,46.89,4746.95
6,Amazon,United States,469.82,B,33.36,B,420.55,1468.4,469.82,33.36,420.55
7,Apple,United States,378.7,B,100.56,B,381.19,2640.32,378.7,100.56,381.19
8,Agricultural Bank of China,China,181.42,B,37.38,B,4561.05,133.38,181.42,37.38,4561.05
9,Bank of America,United States,96.83,B,31,B,3238.22,303.1,96.83,31,3238.22
10,Toyota Motor,Japan,281.75,B,28.15,B,552.46,237.73,281.75,28.15,552.46
11,Alphabet,United States,257.49,B,76.03,B,359.27,1581.72,257.49,76.03,359.27
12,Microsoft,United States,184.9,B,71.19,B,340.39,2054.37,184.9,71.19,340.39
13,Bank of China,China,152.43,B,33.57,B,4192.84,117.83,152.43,33.57,4192.84
14,Samsung Group,South Korea,244.16,B,34.27,B,358.88,367.26,244.16,34.27,358.88
FULL CODE:
//Forbes companies bar chart
//set up chart area
const MARGIN = { LEFT: 250, RIGHT: 10, TOP: 50, BOTTOM: 100 }
const WIDTH = 1000 - MARGIN.LEFT - MARGIN.RIGHT
const HEIGHT = 1100 - MARGIN.TOP - MARGIN.BOTTOM
const svg = d3.select("#chart-area").append("svg")
.attr("width", WIDTH + MARGIN.LEFT + MARGIN.RIGHT)
.attr("height", HEIGHT + MARGIN.TOP + MARGIN.BOTTOM)
const g = svg.append("g")
.attr("transform", `translate(${MARGIN.LEFT}, ${MARGIN.TOP})`)
// X label
g.append("text")
.attr("class", "x axis-label")
.attr("x", WIDTH / 2)
.attr("y", HEIGHT + 50)
.attr("font-size", "20px")
.attr("text-anchor", "middle")
// Y label
const yLabel = g.append("text")
.attr("class", "y axis-label")
.attr("x", - (HEIGHT / 2))
.attr("y", -200)
.attr("font-size", "20px")
.attr("text-anchor", "middle")
.attr("transform", "rotate(-90)")
.text("Company")
//scales
const x = d3.scaleLinear()
.range([0, WIDTH])
const y = d3.scaleBand()
.range([HEIGHT, 0])
//axis generators
const xAxisCall = d3.axisBottom()
const yAxisCall = d3.axisLeft()
//axis groups
const xAxisGroup = g.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0, ${HEIGHT})`)
const yAxisGroup = g.append("g")
.attr("class", "y axis")
//event listeners
$("#var-select").on("change", update)
d3.csv("data/data_clean.csv").then(data => {
data.forEach(d => {
d.sales_usd_billion = Number(d.sales_usd_billion)
d.profit_usd_billion = Number(d.profit_usd_billion)
})
update()
})
function update() {
const t = d3.transition().duration(750)
//filter based on selections
const xValue = $("#var-select").val()
x.domain([0, d3.max(data, d => d[xValue])])
y.domain(data.map(d => d.company))
data.sort(function(a, b) {
return b.rank - a.rank;
})
//update axes
xAxisCall.scale(x)
xAxis.transition(t).call(xAxisCall)
yAxisCall.scale(y)
yAxis.transition(t).call(yAxisCall)
//***Tooltips */
//*** --- */
rects.enter().append("rect")
.attr("y", d => y(d.company) +3)
.attr("x", 0)
.attr("width", d => x(d[value]))
.attr("height", d => 4)
You need to pass data in your update method and change function definition as function update(data). Its a simple scope problem, I would suggest that try debugging the code and then ask for help here.To learn more about debugging, follow a javascript debugging tutorial

TypeError: Cannot read property 'timeFormat' of undefined

I am creating a chart using d3 js and I was trying to convert a DateTime string to time. but it is returning TypeError: Cannot read property 'timeFormat' of undefined
The bellow is the full file that Created.
import {BaseElement} from '../../core/base-element';
import {utilsMixin} from '../../core/mixins/utils-mixin';
// import {LitElement} from 'lit-element';
import * as d3 from 'd3';
export const AreaChartBase = class extends utilsMixin(dataSourceMixin(BaseElement)) {
constructor() {
super();
this.title = '';
this.chartTheme = {
margin : {
top: 35,
right: 145,
bottom: 35,
left: 45
},
barsColors : '#23d160',
tickColor : '#aeaeae',
pathColor : '#aeaeae',
gridColor : '#aeaeae',
barTitleColor : "#23d160",
}
}
static get is() {
return 'area-chart';
}
static get properties() {
return {
title: String,
chartTheme: Object,
}
}
timeFormat(str){
const dateTimeString = new Date(str);
const time = dateTimeString.getTime();
const normalTime = new Date(time).toLocaleDateString("en-US");
return normalTime;
}
areaChart(selector, props, data){
// initialize the variables
const {
margin,
width,
height,
barsColors,
tickColor,
pathColor,
gridColor,
barTitleColor
} = props;
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const svg = d3.select(selector);
let chart = svg.selectAll('svg')
.data([null]);
chart = chart.enter()
.append('svg')
.merge(svg)
// .attr('width', innerWidth)
// .attr('height', innerHeight);
.attr('viewBox', `0,0, ${innerWidth}, ${innerHeight}`)
chart.append('text')
.attr('x', 0)
.attr('y', margin.top / 2)
.attr('text-anchor', 'start')
.style('font-size', '18px')
.attr('fill', '#4c6072')
.text('Area chart');
let g = chart.selectAll('g').data([null]);
g = g.enter()
.append('g')
.merge(g)
.attr('transform', `translate(${margin.left}, ${margin.top})`);
// Add X axis --> it is a date format
const x = d3.scaleBand()
.rangeRound([0, innerWidth])
.padding(0.1);
const y = d3.scaleLinear()
.range([innerHeight - margin.bottom - 17, 0]);
x.domain(data.map(function (d) {
console.log(this.timeFormat(d[0]));
return this.timeFormat(d[0]);
}));
y.domain( d3.extent(data, function(d) { return +d[4]; }) );
const xAxis = d3.axisBottom(x);
const yAxis = d3.axisLeft(y)
.ticks(5)
.tickSizeInner(-innerWidth)
.tickSizeOuter(0)
.tickPadding(10);
let xAxisG = g.selectAll('.x axis').data([null]);
xAxisG = xAxisG.enter()
.append('g')
.attr('class', 'x axis')
.merge(xAxisG)
.attr('transform', `translate(0, ${innerHeight - margin.bottom - 17})`)
let yAxisG = g.selectAll('y axis').data([null]);
yAxisG = yAxisG.enter()
.append('g')
.attr('class', 'y axis')
.merge(yAxisG)
xAxisG.call(xAxis);
xAxisG.selectAll('.tick text')
.attr('fill', tickColor)
xAxisG.selectAll('.tick line')
.attr('stroke', pathColor);
xAxisG.select('.domain')
.attr('stroke', 'transparent');
yAxisG.call(yAxis);
yAxisG.selectAll('.tick text')
.attr('fill', tickColor)
yAxisG.selectAll('.tick line')
.attr('stroke', pathColor);
yAxisG.select('.domain')
.attr('stroke', 'transparent');
// append the svg object to the body of the page
// let svg = d3.select(selector)
// .append("svg")
// .attr("width", width + margin.left + margin.right)
// .attr("height", height + margin.top + margin.bottom)
// .append("g")
// .attr("transform",
// "translate(" + margin.left + "," + margin.top + ")");
// const x = d3.scaleBand()
// .domain(data.map(function (d) {
// return d[0];
// }))
// .range([ 0, width ]);
// svg.append("g")
// .attr("transform", "translate(0," + (height+5) + ")")
// .call(d3.axisBottom(x).ticks(5).tickSizeOuter(0));
// // Add Y axis
// var y = d3.scaleLinear()
// .domain( d3.extent(data, function(d) { return +d[4]; }) )
// .range([ height, 0 ]);
// svg.append("g")
// .attr("transform", "translate(-5,0)")
// .call(d3.axisLeft(y).tickSizeOuter(0));
// Add the area
svg.append("path")
.datum(data)
.attr("fill", "#69b3a2")
.attr("fill-opacity", .3)
.attr("stroke", "none")
.attr("d", d3.area()
.x(function(d) { return x(d[0]) })
.y0( height )
.y1(function(d) { return y(+d[4]) })
)
// Add the line
svg.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "#69b3a2")
.attr("stroke-width", 2)
.attr("d", d3.line()
.x(function(d) { return x(d[0]) })
.y(function(d) { return y(+d[4]) })
)
// Add the line
svg.selectAll("myCircles")
.data(data)
.enter()
.append("circle")
.attr("fill", "red")
.attr("stroke", "none")
.attr("cx", function(d) { return x(d[0]) })
.attr("cy", function(d) { return y(parseInt(d[4])) })
.attr("r", 2)
}
initAreaChart(dsc){
const rows = dsc.rows;
const data = dsc.data;
const cont = this.qs('#chart');
this.areaChart(cont, Object.assign({}, this.chartTheme, {
width: this.qs('#container').clientWidth,
height: 400,
}), rows);
}
firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
let self = this;
this.loader.then(dsc => {
self.initAreaChart(dsc);
});
}
dscDataName() {
return this.e.defaultValue;
}
init(pElement, loader) {
super.init(pElement, loader);
var self = this;
self.loader = this.loadData();
}
}```
if you guys can help me find a fix for the above error I will appreciate it. Thanks in advance
Try to use arrow function. Arrow functions keep context.
x.domain(data.map( (d) => {
console.log(this.timeFormat(d[0]));
return this.timeFormat(d[0]);
}));
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

D3 components do not update in React

I am trying to update some bar graphs when a button is clicked.
Here's the button
<button className="login_buttons" onClick={this.setRecommendations}>
Click to See Top Tracks and Recommendations
</button>
it calls this function, which does successfully update all the states, and right now the graph displays dummy data just fine.
setRecommendations(){
getAlbums().then(albums =>{
this.setState({topAlbums: albums[0]});
this.setState({album_count: albums[1]});
});
getArtists().then(artists =>{
this.setState({topArtist: artists[0]});
this.setState({artist_count: artists[1]});
});
getGenres().then(genres =>{
this.setState({topGenre: genres[0]});
this.setState({genre_count: genres[1]});
});
popularityData().then(popData =>{
this.setState({popRange: popData[0]});
this.setState({pop_count: popData[1]});
});
recommendations().then(recs => {
this.setState({recommendations: recs});
});
}
and here's my Graph component
import React from 'react';
import * as d3 from "d3";
class Graphs extends React.Component {
componentDidMount() {
this.drawChart();
}
componentDidUpdate() {
d3.select(`#${this.props.graphID}`).remove()
this.drawChart();
}
drawChart() {
const data = this.props.data;
const labels = this.props.axisLabels;
const title = this.props.title;
const margins = {top:50, right:50, bottom:50, left:50}
const h = 600 - margins.top - margins.bottom;
const w = 600 - margins.right - margins.left;
var x = d3.scaleBand()
.range([0, w])
.domain(labels);
var y = d3.scaleLinear()
.range([h, 0])
.domain([0, d3.max(data)]);
const svg = d3.select(`#${this.props.graphID}`)
.append("svg")
.attr("width", w + margins.right + margins.left)
.attr("height", h + margins.top + margins.bottom)
.append("g")
.attr("transform", `translate(${margins.left}, ${margins.top})`);
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("y", (d) => y(d))
.attr("x", (d, i) => (x.bandwidth()*i + 10))
.attr("height", (d) => h - y(d))
.attr("width", x.bandwidth() - 10)
.attr("fill", "blue");
svg.append("g")
.attr("transform", `translate(0, ${h})`)
.call(d3.axisBottom(x))
svg.append("g")
.call(d3.axisLeft(y));
svg.append("text")
.attr("x", (w/2))
.attr("y", 0 - (margins.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("fill", "white")
.style("text-decoration", "underline")
.text(title);
}
render(){
return <div id={this.props.graphID} />
}
}
export default Graphs
And when I click the button, the Graphs that have dummy data in them now do actually disappear so componentsWillUpdate is called but it is not redrawing the graphs, and I do not understand why because compnentsDoMount calls this.drawChart() alright.
Don't remove the container:
d3.select(`#${this.props.graphID}`).remove()
Remove what's in it:
d3.select(`#${this.props.graphID}`).selectAll('*').remove()

render bar charts in javascript using svg and 2D array from json

Have spent the last 2 days looking through stackoverflow and online examples as to why my charts aren't displaying properly.
I'm sure I'm missing something in terms of the scaling portion of the code. If I copy the dark part at the bottom of the x-Axis on the chart to notepad it gives me all of the x-axis elements.
Can anyone point me in the right direction?
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.8.0/d3.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded',function(){
req.open("GET",'https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json',true);
req.send();
req.onload=function(){
json=JSON.parse(req.responseText);
document.getElementsByClassName('title')[0].innerHTML=json.name;
dataset=json.data;
const w = 500;
const h = 300;
const padding = 10;
// create an array with all date names
const dates = dataset.map(function(d) {
return d[0];
});
const xScale = d3.scaleBand()
.rangeRound([padding, w-padding])
.padding([.02])
.domain(dates);
console.log("Scale Bandwidth: " + xScale.bandwidth());
const yScale = d3.scaleLinear()
.rangeRound([h-padding, padding])
.domain(0,d3.max(dataset, (d)=>d[1]));
console.log("Dataset Max Height: " + d3.max(dataset, (d)=>d[1]));
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.append("g")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
svg.append("g")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("width",(d,i)=>xScale.bandwidth())
.attr("height",(d,i)=>(h-yScale(d[1])))
.attr("x", (d,i)=>xScale(d[0]))
.attr("y", (d,i)=>yScale(d[1]))
.attr("fill", "navy")
.attr("class", "bar");
};
});
</script>
<h1 class="title">Title Will Go Here</h1>
</body>
D3 now uses Promises instead of asynchronous callbacks to load data. Promises simplify the structure of asynchronous code, especially in modern browsers that support async and await.
Changes in D3 5.0
Also, you are right in that your yScale is broken. Linear scales need a range and a domain, each being passed a 2 value array.
const yScale = d3.scaleLinear()
.range([h - padding, padding])
.domain([0, d3.max(dataset, (d) => d[1])]);
document.addEventListener('DOMContentLoaded', async function() {
const res = await d3.json("https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json");
//console.log(res.data)
const dataset = res.data
const w = 500;
const h = 300;
const padding = 10;
// create an array with all date names
const dates = dataset.map(function(d) {
return d[0];
});
const max = d3.max(dataset, function(d) { return d[1]} )
const xScale = d3.scaleBand()
.rangeRound([0, w])
.padding([.02])
.domain(dates);
console.log("Scale Bandwidth: " + xScale.bandwidth());
const yScale = d3.scaleLinear()
.range([h - padding, padding])
.domain([0, d3.max(dataset, (d) => d[1])]);
console.log("Dataset Max Height: " + d3.max(dataset, (d) => d[1]));
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.append("g")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
svg.append("g")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("width", (d, i) => xScale.bandwidth())
.attr("height", (d, i) => (h - yScale(d[1])) )
.attr("x", (d, i) => xScale(d[0]))
.attr("y", (d, i) => yScale(d[1]))
.attr("fill", "navy")
.attr("class", "bar");
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.8.0/d3.min.js"></script>
Codepen

Why are there no ticks on my X (time) axis?

QUESTION:
Why are there no ticks on my X (time) axis xAxis?
CODE:
<script type="text/javascript">
d3.json("https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/GDP-data.json", function(error, json) {
if (error) {
return console.warn(error);
}
visualizeIt(json);
});
function visualizeIt(data) {
const dataset = data.data;
const margin = {
top: 10,
right: 6,
bottom: 20,
left: 70
}
const w = 900 - margin.left - margin.right;
const h = 500 - margin.top - margin.bottom;
const barWidth = Math.ceil(w / dataset.length);
const format = d3.timeFormat("%Y-%m");
const mindate = dataset[0][0];
const maxdate = dataset[274][0];
const xScale = d3.scaleTime()
.domain([mindate, maxdate])
.range([0, w]);
const yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[1])])
.range([h, 0]);
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale)
const svg = d3.select("#results")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom);
svg.append("g")
.attr("transform", "translate(50," + (h+margin.top) + ")")
.call(xAxis);
svg.append("g")
.attr("transform", "translate(50," + margin.top + ")")
.call(yAxis);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d,i) => 50+barWidth*i)
.attr("y", (d, i) => yScale(d[1]) + margin.top)
.attr("width", barWidth)
.attr("height", (d, i) => h - yScale(d[1]))
.attr("fill", "navy")
.attr("class", "bar");
}
</script>
There are some problems in your code, but I'll address only those related to the x-axis ticks:
You want to parse the strings and return dates, not the other way around. Thus, instead of using timeFormat, your conts format should use timeParse:
const format = d3.timeParse(specifier)
That brings us to the second problem: the specifier in your const format is wrong. You're missing %d here, which is the day, since your strings are like this: "1947-01-01". So, it should be:
const format = d3.timeParse("%Y-%m-%d")
You have to use the parser in your dates:
const mindate = format(dataset[0][0]);
Do the same for maxdate.
Here is your updated CodePen: https://codepen.io/anon/pen/GmjRzY?editors=1010

Categories

Resources