I am trying to implement a D3-delaunay on a linechart that has circles as points through which the line crosses. I think i am doing everything right, but i cant seem to see the overlay lines that show up. What am i doing wrong here? i am using the code on this site (https://observablehq.com/#didoesdigital/9-may-2020-d3-scatterplot-with-voronoi-tooltips) as a reference and to implement it on my linechart. If this reference is not suitable, than please provide another template that is similar to scatter plot which I can implement.
class Linechart extends React.Component {
constructor(props) {
super(props)
this.createLineChart = this.createLineChart.bind(this)
}
metricToPercent(metric) {
return (metric / 2 + 0.5) * 100;
};
scoreToDescrip(score) {
if (score >= 0.6) {
return "Good";
} else if (score >= 0) {
return "Average";
} else {
return "Poor";
}
};
componentDidMount() {
this.createLineChart()
}
componentDidUpdate() {
this.createLineChart()
}
createLineChart() {
var margin = {
top: 85,
right: 60,
bottom: 60,
left: 80
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var node = this.node
var divObj = d3.select(node)
var svgObj = divObj
.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 + ")");
//Read the data
d3.json("https://raw.githubusercontent.com/QamarFarooq/data-for-testing/main/experience_scores.json").then(function(data) {
var dataArrayWithRemovedEmptyDates = []
// Remove all elements in the array that dont have companyReviewDate property
data.forEach(function(d, i) {
if (d.hasOwnProperty('companyReviewDate')) {
dataArrayWithRemovedEmptyDates.push(d)
}
})
// Transform `companyReviewDate` into an actual date
dataArrayWithRemovedEmptyDates.forEach(function(d) {
d.companyReviewDate = new Date(d.companyReviewDate);
})
// Remove all whitespace in CompanyBusinessName, it creates problems
data.forEach(function(d, i) {
d.companyBusinessName = d.companyBusinessName.split(' ').join('');
})
// group the data: I want to draw one line per group
var sumstat = d3.nest()
.key(function(d) {
return d.companyBusinessName;
})
.entries(dataArrayWithRemovedEmptyDates);
console.log(sumstat)
// Define the div for the tooltip
var tooltip = divObj
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.style("background-color", "white")
.style("box-shadow", "0 0 4px #000000")
.style("padding", "10px")
const monthNames = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
var d = new Date();
tooltip.append("div")
.attr("class", "tooltipDate")
.html(monthNames[d.getMonth()] + " " + "(" + d.getFullYear() + ")")
.style("font-size", "20px")
.style("text-align", "center")
tooltip.append("div")
.attr("class", "tooltipName")
.style("text-align", "center")
.style("color", "grey")
tooltip.append("div")
.attr("class", "tooltipTitle")
.style("text-align", "center")
.html("Customer Sentiment")
.style("padding-top", "10px")
tooltip.append("div")
.attr("class", "tooltipScore")
.style("text-align", "center")
.style("color", 'DarkGrey')
.style("font-size", "20px")
tooltip.append("div")
.attr("class", "tooltipPerception")
.style("text-align", "center")
// Add title for linechart
svgObj.append("text")
.attr("text-anchor", "end")
.attr("font-size", 25)
.attr("x", 110)
.attr("y", -50)
.text("Customer Experience Score");
// Add X axis --> it is a date format
var x = d3.scaleTime()
.domain(d3.extent(data, function(d) {
return d.companyReviewDate;
}))
.range([0, width]);
svgObj.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("stroke-width", "0.3")
.style("opacity", "0.5")
.call(d3.axisBottom(x).tickSize(-height).tickFormat('').ticks(6))
// ticks
svgObj.append("g")
.style("opacity", "0.7")
.style("font", "14px times")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(5));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return +d.customerExperienceScore;
})])
.range([height, 0]);
svgObj.append("g")
.attr("stroke-width", "0.3")
.style("opacity", "0.5")
.call(d3.axisLeft(y).tickSize(-width).tickFormat('').ticks(5))
// ticks
svgObj.append("g")
.style("opacity", "0.7")
.style("font", "14px times")
.call(d3.axisLeft(y).ticks(5));
// Add X axis label:
svgObj.append("text")
.attr("text-anchor", "end")
.attr("font-size", 20)
.attr("x", width / 2 + margin.left)
.attr("y", height + 50)
.style("fill", d3.color("grey"))
.text("Company Review Date");
// Add Y axis label:
svgObj.append("text")
.attr("text-anchor", "end")
.attr("font-size", 20)
.attr("transform", "rotate(-90)")
.attr("x", -height / 2 + 40)
.attr("y", -margin.left + 25)
.style("fill", d3.color("grey"))
.text("Customer Experience Score")
// color palette
var key = sumstat.map(function(d) {
return d.key
}) // list of group names
var color = d3.scaleOrdinal()
.domain(key)
.range(['#e41a1c', '#377eb8', '#4daf4a'])
// Add one DOT in the legend for each name.
svgObj.selectAll(".dots")
.data(key)
.enter()
.append("circle")
.attr("cx", function(d, i) {
return 250 + i * 120
})
.attr("cy", -30)
.attr("r", 7)
.style("fill", function(d) {
return color(d)
})
// Add LABEL for legends of each dot.
svgObj.selectAll(".labels")
.data(key)
.enter()
.append("text")
.style("fill", d3.color("grey"))
.attr("x", function(d, i) {
return 270 + i * 120
})
.attr("y", -28)
.text(function(d) {
return d
})
.attr("text-anchor", "left")
.style("alignment-baseline", "middle")
// Highlight individual line and show tooltip
var highlightAndShowTooltip = function(d) {
// this means i am on top of dot circle
if (d.key == null) {
console.log("I am on top of a dot circle of key " + d.companyBusinessName)
//show tooltip
tooltip.style("visibility", "visible")
//Data for Tooltip
tooltip.selectAll(".tooltipName")
.html(d.key)
var score = 12 //this will be dynamic, for now i just set it to 12 to test it out
tooltip.selectAll(".tooltipScore")
.html("<span style='color: #cb9f9e;'>" + score + "</span> / 100")
// first every group turns grey
svgObj.selectAll(".line")
.transition().duration(200)
.style("opacity", "0.5")
svgObj.selectAll(".dot")
.transition().duration(200)
.style("opacity", "0.5")
// Second the hovered line takes its color
svgObj.selectAll("." + d.companyBusinessName)
.transition().duration(200)
.style("stroke", color(d.companyBusinessName))
.style("opacity", "1")
svgObj.selectAll("." + d.companyBusinessName)
.transition().duration(200)
.style("stroke", color(d.companyBusinessName))
.style("opacity", "1")
}
// this means i am on top of line
else if (d.companyBusinessName == null) {
var selected_line = d.key
console.log("i am on top of line " + d.key)
// first every group turns grey
svgObj.selectAll(".line")
.transition().duration(200)
.style("opacity", "0.5")
svgObj.selectAll(".dot")
.transition().duration(200)
.style("opacity", "0.5")
// Second the hovered line takes its color
svgObj.selectAll("." + selected_line)
.transition().duration(200)
.style("stroke", color(selected_line))
.style("opacity", "1")
svgObj.selectAll("." + selected_line)
.transition().duration(200)
.style("stroke", color(selected_line))
.style("opacity", "1")
}
}
// UnHighlight and hide tooltip
var doNotHighlightAndHideTooltip = function(d) {
//hide tooltip
tooltip.style("visibility", "hidden")
//return other lines back to normal opacity
svgObj.selectAll(".line")
.transition().duration(200).delay(50)
.style("stroke", function(d) {
return (color(d.key))
})
.style("opacity", "1")
svgObj.selectAll(".dot")
.transition().duration(200).delay(50)
.style("stroke", function(d) {
return (color(d.companyBusinessName))
})
.style("opacity", "1")
}
// keep showing tooltip as cursor moves along line
var keepShowingTooltip = function(d) {
tooltip.style("top", (d3.event.pageY - 10) + "px").style("left", (d3.event.pageX + 10) + "px")
}
// Draw the line
svgObj.selectAll(".line")
.data(sumstat)
.enter()
.append("path")
.attr("class", function(d) {
return "line " + d.key
}) // 2 class for each line: 'line' and the group name
.attr("fill", "none")
.attr("stroke", function(d) {
return color(d.key)
})
.attr("stroke-width", 4.5)
.attr("d", function(d) {
return d3.line()
.curve(d3.curveMonotoneX)
.x(function(d) {
return x(d.companyReviewDate);
})
.y(function(d) {
return y(+d.customerExperienceScore);
})
(d.values)
});
// This is the experiment with voronoi
// Draw dots on points
svgObj.selectAll(".dot")
//i am using the raw data array, NOT the nested array
.data(dataArrayWithRemovedEmptyDates)
.enter()
.append("circle")
.attr("class", function(d, i) {
return i;
})
.style("fill", "white")
.style("stroke-width", "3px")
.style("stroke", function(d) {
return color(d.companyBusinessName)
})
.attr("cx", function(d) {
return x(d.companyReviewDate);
})
.attr("cy", function(d) {
return y(d.customerExperienceScore);
})
.attr("r", 5.5)
var voronoi = d3.Delaunay
.from(dataArrayWithRemovedEmptyDates, d => x(d.x), d => y(d.y))
.voronoi([margin.left, margin.top, width - margin.right, height - margin.bottom]); // ensures voronoi is limited to the
//Create the voronoi grid
svgObj.append("g")
.attr("class", "voronoiWrapper")
.selectAll("path")
.data(dataArrayWithRemovedEmptyDates)
.enter()
.append("path")
.attr("opacity", 0.5)
.attr("stroke", "#ff1493") // Hide overlay
.attr("fill", "none")
.style("pointer-events", "all")
.attr("d", (d, i) => voronoi.renderCell(i))
.on("mouseover", highlightAndShowTooltip)
.on("mouseout", doNotHighlightAndHideTooltip);
})
}
render() {
return <div ref = {
node => this.node = node
}
className = "example_div" > < /div>
}
}
ReactDOM.render( <Linechart / >, document.querySelector('body'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://unpkg.com/d3-delaunay#5.3.0/dist/d3-delaunay.min.js"></script>
You didn't look at your data structure well enough: you don't use d.x and d.y, you have your own names for those:
.from(dataArrayWithRemovedEmptyDates, d => x(d.companyReviewDate), d => y(d.customerExperienceScore))
I also removed the margins from the Voronoi. The reasoning is that svgObj is not an svg for you, but a g with a transform. That means that you already applied the margins correctly, so if you add them here, you apply them twice. All this yields the following result:
class Linechart extends React.Component {
constructor(props) {
super(props)
this.createLineChart = this.createLineChart.bind(this)
}
metricToPercent(metric) {
return (metric / 2 + 0.5) * 100;
};
scoreToDescrip(score) {
if (score >= 0.6) {
return "Good";
} else if (score >= 0) {
return "Average";
} else {
return "Poor";
}
};
componentDidMount() {
this.createLineChart()
}
componentDidUpdate() {
this.createLineChart()
}
createLineChart() {
var margin = {
top: 85,
right: 60,
bottom: 60,
left: 80
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var node = this.node
var divObj = d3.select(node)
var svgObj = divObj
.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 + ")");
//Read the data
d3.json("https://raw.githubusercontent.com/QamarFarooq/data-for-testing/main/experience_scores.json").then(function(data) {
var dataArrayWithRemovedEmptyDates = []
// Remove all elements in the array that dont have companyReviewDate property
data.forEach(function(d, i) {
if (d.hasOwnProperty('companyReviewDate')) {
dataArrayWithRemovedEmptyDates.push(d)
}
})
// Transform `companyReviewDate` into an actual date
dataArrayWithRemovedEmptyDates.forEach(function(d) {
d.companyReviewDate = new Date(d.companyReviewDate);
})
// Remove all whitespace in CompanyBusinessName, it creates problems
data.forEach(function(d, i) {
d.companyBusinessName = d.companyBusinessName.split(' ').join('');
})
// group the data: I want to draw one line per group
var sumstat = d3.nest()
.key(function(d) {
return d.companyBusinessName;
})
.entries(dataArrayWithRemovedEmptyDates);
console.log(sumstat)
// Define the div for the tooltip
var tooltip = divObj
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.style("background-color", "white")
.style("box-shadow", "0 0 4px #000000")
.style("padding", "10px")
const monthNames = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
var d = new Date();
tooltip.append("div")
.attr("class", "tooltipDate")
.html(monthNames[d.getMonth()] + " " + "(" + d.getFullYear() + ")")
.style("font-size", "20px")
.style("text-align", "center")
tooltip.append("div")
.attr("class", "tooltipName")
.style("text-align", "center")
.style("color", "grey")
tooltip.append("div")
.attr("class", "tooltipTitle")
.style("text-align", "center")
.html("Customer Sentiment")
.style("padding-top", "10px")
tooltip.append("div")
.attr("class", "tooltipScore")
.style("text-align", "center")
.style("color", 'DarkGrey')
.style("font-size", "20px")
tooltip.append("div")
.attr("class", "tooltipPerception")
.style("text-align", "center")
// Add title for linechart
svgObj.append("text")
.attr("text-anchor", "end")
.attr("font-size", 25)
.attr("x", 110)
.attr("y", -50)
.text("Customer Experience Score");
// Add X axis --> it is a date format
var x = d3.scaleTime()
.domain(d3.extent(data, function(d) {
return d.companyReviewDate;
}))
.range([0, width]);
svgObj.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("stroke-width", "0.3")
.style("opacity", "0.5")
.call(d3.axisBottom(x).tickSize(-height).tickFormat('').ticks(6))
// ticks
svgObj.append("g")
.style("opacity", "0.7")
.style("font", "14px times")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(5));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return +d.customerExperienceScore;
})])
.range([height, 0]);
svgObj.append("g")
.attr("stroke-width", "0.3")
.style("opacity", "0.5")
.call(d3.axisLeft(y).tickSize(-width).tickFormat('').ticks(5))
// ticks
svgObj.append("g")
.style("opacity", "0.7")
.style("font", "14px times")
.call(d3.axisLeft(y).ticks(5));
// Add X axis label:
svgObj.append("text")
.attr("text-anchor", "end")
.attr("font-size", 20)
.attr("x", width / 2 + margin.left)
.attr("y", height + 50)
.style("fill", d3.color("grey"))
.text("Company Review Date");
// Add Y axis label:
svgObj.append("text")
.attr("text-anchor", "end")
.attr("font-size", 20)
.attr("transform", "rotate(-90)")
.attr("x", -height / 2 + 40)
.attr("y", -margin.left + 25)
.style("fill", d3.color("grey"))
.text("Customer Experience Score")
// color palette
var key = sumstat.map(function(d) {
return d.key
}) // list of group names
var color = d3.scaleOrdinal()
.domain(key)
.range(['#e41a1c', '#377eb8', '#4daf4a'])
// Add one DOT in the legend for each name.
svgObj.selectAll(".dots")
.data(key)
.enter()
.append("circle")
.attr("cx", function(d, i) {
return 250 + i * 120
})
.attr("cy", -30)
.attr("r", 7)
.style("fill", function(d) {
return color(d)
})
// Add LABEL for legends of each dot.
svgObj.selectAll(".labels")
.data(key)
.enter()
.append("text")
.style("fill", d3.color("grey"))
.attr("x", function(d, i) {
return 270 + i * 120
})
.attr("y", -28)
.text(function(d) {
return d
})
.attr("text-anchor", "left")
.style("alignment-baseline", "middle")
// Highlight individual line and show tooltip
var highlightAndShowTooltip = function(d) {
// this means i am on top of dot circle
if (d.key == null) {
console.log("I am on top of a dot circle of key " + d.companyBusinessName)
//show tooltip
tooltip.style("visibility", "visible")
//Data for Tooltip
tooltip.selectAll(".tooltipName")
.html(d.key)
var score = 12 //this will be dynamic, for now i just set it to 12 to test it out
tooltip.selectAll(".tooltipScore")
.html("<span style='color: #cb9f9e;'>" + score + "</span> / 100")
// first every group turns grey
svgObj.selectAll(".line")
.transition().duration(200)
.style("opacity", "0.5")
svgObj.selectAll(".dot")
.transition().duration(200)
.style("opacity", "0.5")
// Second the hovered line takes its color
svgObj.selectAll("." + d.companyBusinessName)
.transition().duration(200)
.style("stroke", color(d.companyBusinessName))
.style("opacity", "1")
svgObj.selectAll("." + d.companyBusinessName)
.transition().duration(200)
.style("stroke", color(d.companyBusinessName))
.style("opacity", "1")
}
// this means i am on top of line
else if (d.companyBusinessName == null) {
var selected_line = d.key
console.log("i am on top of line " + d.key)
// first every group turns grey
svgObj.selectAll(".line")
.transition().duration(200)
.style("opacity", "0.5")
svgObj.selectAll(".dot")
.transition().duration(200)
.style("opacity", "0.5")
// Second the hovered line takes its color
svgObj.selectAll("." + selected_line)
.transition().duration(200)
.style("stroke", color(selected_line))
.style("opacity", "1")
svgObj.selectAll("." + selected_line)
.transition().duration(200)
.style("stroke", color(selected_line))
.style("opacity", "1")
}
}
// UnHighlight and hide tooltip
var doNotHighlightAndHideTooltip = function(d) {
//hide tooltip
tooltip.style("visibility", "hidden")
//return other lines back to normal opacity
svgObj.selectAll(".line")
.transition().duration(200).delay(50)
.style("stroke", function(d) {
return (color(d.key))
})
.style("opacity", "1")
svgObj.selectAll(".dot")
.transition().duration(200).delay(50)
.style("stroke", function(d) {
return (color(d.companyBusinessName))
})
.style("opacity", "1")
}
// keep showing tooltip as cursor moves along line
var keepShowingTooltip = function(d) {
tooltip.style("top", (d3.event.pageY - 10) + "px").style("left", (d3.event.pageX + 10) + "px")
}
// Draw the line
svgObj.selectAll(".line")
.data(sumstat)
.enter()
.append("path")
.attr("class", function(d) {
return "line " + d.key
}) // 2 class for each line: 'line' and the group name
.attr("fill", "none")
.attr("stroke", function(d) {
return color(d.key)
})
.attr("stroke-width", 4.5)
.attr("d", function(d) {
return d3.line()
.curve(d3.curveMonotoneX)
.x(function(d) {
return x(d.companyReviewDate);
})
.y(function(d) {
return y(+d.customerExperienceScore);
})
(d.values)
});
// This is the experiment with voronoi
// Draw dots on points
svgObj.selectAll(".dot")
//i am using the raw data array, NOT the nested array
.data(dataArrayWithRemovedEmptyDates)
.enter()
.append("circle")
.attr("class", function(d, i) {
return i;
})
.style("fill", "white")
.style("stroke-width", "3px")
.style("stroke", function(d) {
return color(d.companyBusinessName)
})
.attr("cx", function(d) {
return x(d.companyReviewDate);
})
.attr("cy", function(d) {
return y(d.customerExperienceScore);
})
.attr("r", 5.5)
var voronoi = d3.Delaunay
.from(dataArrayWithRemovedEmptyDates, d => x(d.companyReviewDate), d => y(d.customerExperienceScore))
.voronoi([0, 0, width, height]); // ensures voronoi is limited to the
console.log(voronoi, dataArrayWithRemovedEmptyDates);
//Create the voronoi grid
svgObj.append("g")
.attr("class", "voronoiWrapper")
.selectAll("path")
.data(dataArrayWithRemovedEmptyDates)
.enter()
.append("path")
.attr("opacity", 0.5)
.attr("stroke", "#ff1493") // Hide overlay
.attr("fill", "none")
.style("pointer-events", "all")
.attr("d", (d, i) => {
return voronoi.renderCell(i);
})
.on("mouseover", highlightAndShowTooltip)
.on("mouseout", doNotHighlightAndHideTooltip);
})
}
render() {
return <div ref = {
node => this.node = node
}
className = "example_div" > < /div>
}
}
ReactDOM.render( <Linechart / >, document.querySelector('body'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://unpkg.com/d3-delaunay#5.3.0/dist/d3-delaunay.min.js"></script>
I am trying to create D3 charts based on elasticsearch nested aggregation. While I can plot a chart based on the first aggregation, I am not sure why I cant plot a similar chart on the nested a subsequent aggregation. Can anyone suggest what I should do?
My code:
esClient.search({
index: 'vehicle',
body: {
query:{
match:{
_all:searchTerms
}
},
aggs: {
touchdowns: {
terms: {
field: "country",
size:5
},
aggs: {
corp: {
terms: {
field: "companyName",
size:5
}
}
},
}
},
sort: [sortObject],
from: resultsPage * 10,
}
}).then(function(es_return){
deferred.resolve(es_return);
//// Pie Chart first aggregation
var touchdowns = es_return.aggregations.touchdowns.buckets;
// d3 donut chart
var width = 600,
height = 300,
radius = Math.min(width, height) / 2;
var color = ['#ff7f0e', '#d62728', '#2ca02c', '#1f77b4'];
var arc = d3.svg.arc()
.outerRadius(radius - 60)
.innerRadius(120);
var pie = d3.layout.pie()
.sort(null)
.value(function (d) { return d.doc_count; });
var svg = d3.select("#donut-chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/1.4 + "," + height/2 + ")");
var g = svg.selectAll(".arc")
.data(pie(touchdowns))
.enter()
.append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function (d, i) { return color[i]; });
g.append("text")
.attr("transform", function (d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.style("fill", "white")
.text(function (d) { return d.data.key; });
//// Pie Chart
var touchdowns = es_return.aggregations.touchdowns.buckets.corp.buckets;
// d3 donut chart
var width = 600,
height = 300,
radius = Math.min(width, height) / 2;
var color = ['#ff7f0e', '#d62728', '#2ca02c', '#1f77b4'];
var arc = d3.svg.arc()
.outerRadius(radius - 60)
.innerRadius(120);
var pie = d3.layout.pie()
.sort(null)
.value(function (d) { return d.doc_count; });
var svg = d3.select("#donut-chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/1.4 + "," + height/2 + ")");
var g = svg.selectAll(".arc")
.data(pie(touchdowns))
.enter()
.append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function (d, i) { return color[i]; });
g.append("text")
.attr("transform", function (d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.style("fill", "white")
.text(function (d) { return d.assignee.key; });
}, function(error){
deferred.reject(error);
});
return deferred.promise;};
SO community,
I'm making a D3 histogram as an Angular directive and I want it to be able to change/update accordingly as the data it reads in changes. In other words, I am using Angular to watch the changes in data and (hope to) redraw the histogram every time the data is changed.
This might mostly be a question about D3's updating and binding of data because the $watchCollection seems to work fine. Even though I have went through this tutorial on adding elements to a d3 chart, I still cannot apply it on my histogram. I think the way the elements in my histogram are nested is really confusing me...
Context: Ideally this histogram will read from an array to which data returned from several Ajax calls will get stored in. So every time a new set of data arrive, the histogram will grow itself another bar. That's why I would love to know how to update the chart as well as the x-axis properly.
Thank you! :)
The JS fiddle is here: http://jsfiddle.net/santina/wrtenjny/1/
Code for just the d3 part is here, largely taken from mbostock's sortable bar chart.
// Aesthetic settings
var margin = {top: 20, right: 50, bottom: 20, left: 50},
width = document.getElementById('performance').clientWidth - margin.left - margin.right ||
940 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
barColor = "steelblue",
axisColor = "whitesmoke",
axisLabelColor = "grey",
yText = "# QUERIES",
xText = "BEACON IDs";
// Inputs to the d3 graph
var data = scope[attrs.data];
// A formatter for counts.
var formatCount = d3.format(",.0f");
// Set the scale, separate the first bar by a bar width from y-axis
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(formatCount);
// Initialize histogram
var svg = d3.select(".histogram-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
function drawAxis(){
data.forEach(function(d) {
d.nqueries = +d.nqueries;
});
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, d3.max(data, function(d) { return d.nqueries; })]);
// Draw x-axis
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("y", 6)
.attr("dy", "-0.71em")
.attr("x", width )
.style("text-anchor", "end")
.style("fill", axisLabelColor)
.text(xText);
// Draw y-axis
svg.append("g")
.attr("class", "y-axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style("fill", axisLabelColor)
.text(yText);
// Change axis color
d3.selectAll("path").attr("fill", axisColor);
}
function updateAxis(){
console.log(data);
data.forEach(function(d) {
d.nqueries = +d.nqueries;
});
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, d3.max(data, function(d) { return d.nqueries; })]);
svg.selectAll("g.y_axis").call(yAxis);
svg.selectAll("g.x_axis").call(xAxis);
}
function drawHistogram(){
drawAxis();
var bar = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "barInfo");
bar.append("rect")
.attr("class", "bar")
.attr("x", function(d){ return x(d.name) })
.attr("width", x.rangeBand())
.attr("y", function(d){ return y(d.nqueries) })
.attr("height", function(d) { return height - y(d.nqueries); })
.attr("fill", barColor);
bar.append("text")
.attr("y", function(d){ return y(d.nqueries) })
.attr("x", function(d){ return x(d.name) })
.attr("dy", "-1px")
.attr("dx", x.rangeBand()/2 )
.attr("text-anchor", "middle")
.attr("class", "numberLabel")
.text(function(d) { return formatCount(d.nqueries); });
}
// Doesn't work :(
function updateHistogram(){
console.log("updating");
// Redefine scale and update axis
updateAxis();
// Select
var bar = svg.selectAll(".barInfo").data(data);
// Update - rect
var rects = bar.selectAll("rect")
.attr("class", "bar")
.attr("x", function(d){ return x(d.name) })
.attr("width", x.rangeBand());
// Update
var texts = bar.selectAll("text")
.attr("x", function(d){ return x(d.name) })
.attr("dx", x.rangeBand()/2 );
// Enter
bar.enter().append("g")
.attr("class", "bar").selectAll("rect").append("rect")
.attr("class", "bar")
.attr("x", function(d){ return x(d.name) })
.attr("width", x.rangeBand())
.attr("y", function(d){ return y(d.nqueries) })
.attr("height", function(d) { return height - y(d.nqueries); })
.attr("fill", barColor);
bar.enter().append("g")
.attr("class", "bar").selectAll("text").append("text")
.attr("y", function(d){ return y(d.nqueries) })
.attr("x", function(d){ return x(d.name) })
.attr("dy", "-1px")
.attr("dx", x.rangeBand()/2 )
.attr("text-anchor", "middle")
.attr("class", "numberLabel")
.text(function(d) { return formatCount(d.nqueries); });
}
drawHistogram();
First, you got the wrong class selector when you update your axis:
svg.selectAll("g.y-axis").call(yAxis); //<-- dash not underscore
svg.selectAll("g.x-axis").call(xAxis);
Second, you were close on your update, but we can clean it up a bit:
// select on what you originally binded data to
var bar = svg.selectAll(".barInfo").data(data);
// for data entering
var bEnter = bar.enter().append("g")
.attr("class", "barInfo");
// append a rect
bEnter.append("rect")
.attr("class", "bar");
// and the text elements
bEnter.append("text")
.attr("class","numberLabel");
// now we can update everybody together
bar.select("rect")
.attr("x", function(d){ return x(d.name) })
.attr("width", x.rangeBand())
.attr("y", function(d){ return y(d.nqueries) })
.attr("height", function(d) { return height - y(d.nqueries); })
.attr("fill", barColor);
bar.select("text")
.attr("y", function(d){ return y(d.nqueries) })
.attr("x", function(d){ return x(d.name) })
.attr("dy", "-1px")
.attr("dx", x.rangeBand()/2 )
.attr("text-anchor", "middle")
.attr("class", "numberLabel")
.text(function(d) { return formatCount(d.nqueries); });
Udpated example here.
EDITS
Opps, I'm not selecting correctly on my updates.
bar.selectAll("rect")
Should be:
bar.select("rect")
This fixes both the updates and the sorting...
Updated fiddle.
Also notice that I collapsed your code further. With your angular watch you really don't need a seperate draw and update function, one can do both.
I am developing bar graph using d3.js integrating with Angularjs. The angular directive code I am using is given below . My problem is ,in my real application , the bar graph is not redrawing but its axis changes when data associated with the bar graph changes
mainApp.directive('ngDatenobargraph', function() {
return {
restrict: 'AE',
scope: {
data: '='
},
link: function (scope, element) {
var margin = {top: 10, right: 10, bottom: 40, left: 60},
width = 410 - margin.left - margin.right,
height = 230 - margin.top - margin.bottom;
var chart = d3.select(element[0])
.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 + ")");
var x = d3.scale.ordinal().rangeRoundBands([0, width], .1);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<span style='font-size: 13px'>Date: </span><span style='color:red;font-size: 13px'>" + d.xValue+ "</span><br>"+
"<span style='font-size: 13px'>Net Value: </span><span style='color:red;font-size: 13px'>" + d.yValue+ "</span><br>"+
"<span style='font-size: 13px'>Buy Value: </span><span style='color:red;font-size: 13px'>" + d.buyValue+ "</span><br>"+
"<span style='font-size: 13px'>Sell Value: </span><span style='color:red;font-size: 13px'>" + d.sellValue+ "</span>";
});
chart.call(tip);
//Render graph based on 'data'
scope.render = function(data) {
if(data) {
var y = d3.scale.linear()
.range([height, 0])
.domain(d3.extent(data, function(d) { return d.yValue; }))
.nice();
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(7);
x.domain(data.map(function(d) { return d.xValue; }));
//Redraw the axes
chart.selectAll('g.axis').remove();
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height) + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", function(d) {
return "rotate(-20)";
});
chart.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0-margin.left)
.attr("x",0-(height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Net Value");
chart.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", function(d) { return d.yValue < 0 ? "bar negative" : "bar positive"; })
.attr("x", function(d) { return x(d.xValue); })
.attr("y", height)
.attr("height", 0)
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.transition().duration(2000)
.attr("y", function(d) {return y(Math.max(0, d.yValue)); })
.attr("height", function(d) {return Math.abs(y(d.yValue) - y(0)); })
// .attr("width", x.rangeBand());
.attr("width", Math.min.apply(null, [x.rangeBand()-2, 100]));
}
};
scope.$watch('data', function() {
scope.render(scope.data);
}, true);
}
};
});
I am new to d3.js . If any have solution please help me .The code in application is given http://jsfiddle.net/HB7LU/9000/