D3 V4 multi-line chart with scatterplot in angular-cli - javascript

I am using D3 charting library to create charts with Angular-cli. D3 version is 4.2.2. Following is what I am trying to create multi-line chart.
import {Directive, ElementRef} from '#angular/core';
import * as D3 from 'd3';
#Directive({
selector: 'bar-graph'
})
export class BarGraphDirective {
private htmlElement:HTMLElement;
constructor(elementRef:ElementRef) {
this.htmlElement = elementRef.nativeElement; // reference to <bar-graph> element from the main template
console.log(this.htmlElement);
console.log(D3);
let d3:any = D3;
var data = [{
"date": "2016-10-01",
"sales": 110,
"searches": 67
}, {
"date": "2016-10-02",
"sales": 120,
"searches": 67
}, {
"date": "2016-10-03",
"sales": 125,
"searches": 69.4
}, {
"date": "2016-10-04",
"sales": 100,
"searches": 67
},{
"date": "2016-10-05",
"sales": 99,
"searches": 66
},{
"date": "2016-10-06",
"sales": 131,
"searches": 67
},{
"date": "2016-10-07",
"sales": 111,
"searches": 47
},{
"date": "2016-10-08",
"sales": 110,
"searches": 67
},{
"date": "2016-10-09",
"sales": 130,
"searches": 67
},{
"date": "2016-10-10",
"sales": 110,
"searches": 67
},{
"date": "2016-10-11",
"sales": 110,
"searches": 67
}];
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 80,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseDate = d3.timeParse("%Y-%m-%d");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the line
var line = d3.line()
.x(function (d) {
return x(d.date);
})
.y(function (d) {
return y(d.sales);
});
var svg = d3.select(this.htmlElement).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 + ")");
// format the data
data.forEach(function (d) {
d.date = parseDate(d.date);
});
x.domain(d3.extent(data, function (d) {
return d.date;
}));
y.domain([0, d3.max(data, function (d) {
return d.sales > d.searches ? d.sales : d.searches;
})]);
// Add the line path.
svg.append("path")
.attr("class", "line")
.style("fill", "none")
.attr("d", line(data))
.style("stroke", "orange");
// change line to look at searches
line.y(function (d) {
return y(d.searches);
});
// Add the second line path.
svg.append("path")
.attr("class", "line")
.style("fill", "none")
.attr("d", line(data))
.style("stroke", "steelblue");
// Add the scatterplot
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.sales); });
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Price ($)");
}
}
Then my chart looks as below.
How to add scatterplots to both lines and how to change color of scatterplots as same as of the line ?
Any suggestions are highly appreciated.
Thank You

// Add sales to the scatterplot
svg.selectAll(".sales-circle")
.data(data)
.enter().append("circle")
.attr('class', 'sales-circle')
.attr("r", 5)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.sales); })
.style("fill", "orange");
// Add searches to the scatterplot
svg.selectAll(".searches-circle")
.data(data)
.enter().append("circle")
.attr("r", 5)
.attr('class', 'searches-circle')
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d. searches); })
.style("fill", "steelblue");

Related

D3 line chart line selected on hoover even if opacity of the line is zero

I have a D3 line chart in my application and on hovering on the line a text is shown above the line displaying the line name. After that I had implemented a legend alongside with the chart and legend allows making lines appear and disappear upon clicking on legend item. My Sample code is given below. But now it has caused a problem like this. As now I'm setting the opacity of the line which was selected from legend to zero, the line gets disappeared. But if I hover in the area where the line was earlier still it shows the on hover text. Does anyone know a solution to avoid this problem.
You can see this issue by clicking on both items in the legend, Then all lines get disappeared. But if you move the mouse over the chart, where the lines were earlier, you will see that text will show on hovering
Example : https://jsfiddle.net/yasirunilan/ymavtsbj/4/
var data = [{
name: "USA",
values: [{
date: "2000",
price: "100"
},
{
date: "2001",
price: "110"
},
{
date: "2002",
price: "145"
},
{
date: "2003",
price: "241"
},
{
date: "2004",
price: "101"
},
{
date: "2005",
price: "90"
},
{
date: "2006",
price: "10"
},
{
date: "2007",
price: "35"
},
{
date: "2008",
price: "21"
},
{
date: "2009",
price: "201"
}
]
},
{
name: "Canada",
values: [{
date: "2000",
price: "100"
},
{
date: "2001",
price: "110"
},
{
date: "2002",
price: "145"
},
{
date: "2003",
price: "241"
},
{
date: "2004",
price: "101"
},
{
date: "2005",
price: "90"
},
{
date: "2006",
price: "10"
},
{
date: "2007",
price: "35"
},
{
date: "2008",
price: "21"
},
{
date: "2009",
price: "201"
}
]
}
];
var width = 500;
var height = 300;
var margin = 50;
var duration = 250;
var lineOpacity = "0.25";
var lineOpacityHover = "0.85";
var otherLinesOpacityHover = "0.1";
var lineStroke = "1.5px";
var lineStrokeHover = "2.5px";
var circleOpacity = '0.85';
var circleOpacityOnLineHover = "0.25"
var circleRadius = 3;
var circleRadiusHover = 6;
/* Format Data */
var parseDate = d3.timeParse("%Y");
data.forEach(function(d) {
d.values.forEach(function(d) {
d.date = parseDate(d.date);
d.price = +d.price;
});
});
/* Scale */
var xScale = d3.scaleTime()
.domain(d3.extent(data[0].values, d => d.date))
.range([0, width - margin]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data[0].values, d => d.price)])
.range([height - margin, 0]);
var color = d3.scaleOrdinal(d3.schemeCategory10);
/* Add SVG */
var svg = d3.select("#chart").append("svg")
.attr("width", (width + margin) + "px")
.attr("height", (height + margin) + "px")
.append('g')
.attr("transform", `translate(${margin}, ${margin})`);
/* Add line into SVG */
var line = d3.line()
.x(d => xScale(d.date))
.y(d => yScale(d.price));
let lines = svg.append('g')
.attr('class', 'lines');
lines.selectAll('.line-group')
.data(data).enter()
.append('g')
.attr('id', d => d.name.replace(/\s+/g, '') + "-line")
.attr('class', 'line-group')
.on("mouseover", function(d, i) {
svg.append("text")
.attr("class", "title-text")
.attr("font-weight", "bold")
.style("fill", color(i))
.text(d.name)
.attr("text-anchor", "middle")
.attr("x", (width - margin) / 2)
.attr("y", -30);
})
.on("mouseout", function(d) {
svg.select(".title-text").remove();
})
.append('path')
.attr('class', 'line')
.attr('d', d => line(d.values))
.style('stroke', d => color(d.name))
.style('opacity', lineOpacity)
.on("mouseover", function(d) {
d3.selectAll('.line')
.style('opacity', otherLinesOpacityHover);
d3.selectAll('.circle')
.style('opacity', circleOpacityOnLineHover);
d3.select(this)
.style('opacity', lineOpacityHover)
.style("stroke-width", lineStrokeHover)
.style("cursor", "pointer");
})
.on("mouseout", function(d) {
d3.selectAll(".line")
.style('opacity', lineOpacity);
d3.selectAll('.circle')
.style('opacity', circleOpacity);
d3.select(this)
.style("stroke-width", lineStroke)
.style("cursor", "none");
});
/* Add circles in the line */
lines.selectAll("circle-group")
.data(data).enter()
.append("g")
.attr('id', d => d.name.replace(/\s+/g, '') + "-circle")
.style("fill", d => color(d.name))
.selectAll("circle")
.data(d => d.values).enter()
.append("g")
.attr("class", "circle")
.on("mouseover", function(d) {
d3.select(this)
.style("cursor", "pointer")
.append("text")
.attr("class", "text")
.text(`${d.price}`)
.attr("x", d => xScale(d.date) + 5)
.attr("y", d => yScale(d.price) - 10);
})
.on("mouseout", function(d) {
d3.select(this)
.style("cursor", "none")
.transition()
.duration(duration)
.selectAll(".text").remove();
})
.append("circle")
.attr("cx", d => xScale(d.date))
.attr("cy", d => yScale(d.price))
.attr("r", circleRadius)
.style('opacity', circleOpacity)
.on("mouseover", function(d) {
d3.select(this)
.transition()
.duration(duration)
.attr("r", circleRadiusHover);
})
.on("mouseout", function(d) {
d3.select(this)
.transition()
.duration(duration)
.attr("r", circleRadius);
});
/* Add Axis into SVG */
var xAxis = d3.axisBottom(xScale).ticks(5);
var yAxis = d3.axisLeft(yScale).ticks(5);
svg.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0, ${height-margin})`)
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append('text')
.attr("y", 15)
.attr("transform", "rotate(-90)")
.attr("fill", "#000")
.text("Total values");
var keys = []
// Add the Legend
var legend = d3.select("body").selectAll(".legend")
.data(data.map(d => d.name))
.enter().append("font")
.attr("class", "legend") // style the legend
.style("color", color)
.style("margin-left", 10 + "px")
.style("padding-left", 10 + "px")
.html(d => d)
d3.selectAll(".legend")
.on("click", function(d) {
keys.includes(d) ?
keys.splice(keys.indexOf(d), 1) :
keys.push(d)
d3.select(this).style("opacity", () => keys.includes(d) ? .5 : 1)
d3.select("#" + d.replace(/\s+/g, '') + "-line")
.transition().duration(100)
.style("opacity", () => keys.includes(d) ? 0 : 1);
d3.select("#" + d.replace(/\s+/g, '') + "-circle")
.transition().duration(100)
.style("opacity", () => keys.includes(d) ? 0 : 1);
})
You can set the CSS pointer-events to none in order to disable all mouse interactions and the set it to auto when you change the opacity back to 1 to enable them.
You can do it using this line:
.style("pointer-events", () => keys.includes(d) ? "none" : "auto");
Working demo: JsFiddle
Alternatively you can replace the lines that sets the opacity and the pointer-events by just setting the display css property like so:
.style("display", () => keys.includes(d) ? "none" : "inline");
Working demo: JsFiddle

Set custom tick value on d3v4 bat charts from JSON data

I am trying to get values from a JSON File and use them as the X and Y axis on a d3 v4 bar chart. But my axis is not showing up as I like. WHat am I doing wrong?
My HTML Code:
<div class="col" id="main-chart" style="padding-top:75px;">
<svg width="675" height="415"></svg>
</div>
My D3 Code:
var svg = d3.select("#main-chart svg"),
margin = {top: 20, right: 20, bottom: 30, left: 75},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
y = d3.scaleLinear().rangeRound([height, 0]);
var colours = d3.scaleOrdinal().range(["#6F257F", "#CA0D59"]);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json(data.json, function(error, data) {
if (error) throw error;
x.domain(data.map(function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.values; })]);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
// .attr("transform", "rotate(-90deg)")
.call(d3.axisBottom(x).ticks(5));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).ticks(5).tickFormat(function(d) {return parseInt(d); }).tickSizeInner([-width]))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.attr("fill", "#5D6971");
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("x", function(d) {return x(d.date); })
.attr("y", function(d) {return y(d.values); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.values); })
.attr("fill", function(d) { return colours(d.date); })
.on("mousemove", function(d){
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "inline-block")
.html((d.date) + "<br>" + (d.values));
})
.on("mouseout", function(d){ tooltip.style("display", "none");});
});
My JSON CODE:
[
{
"date": "2018-10-19",
"values": 6574406
},
{
"date": "2018-10-20",
"values": 6575406
},
{
"date": "2018-10-21",
"values": 6575696
},
{
"date": "2018-10-22",
"values": 6576656
},
{
"date": "2018-10-23",
"values": 6577222
},
{
"date": "2018-10-24",
"values": 6578908
},
{
"date": "2018-10-25",
"values": 6579386
},
{
"date": "2018-10-26",
"values": 6580020
},
{
"date": "2018-10-27",
"values": 6580214
},
{
"date": "2018-10-28",
"values": 6580440
},
{
"date": "2018-10-29",
"values": 6581334
},
{
"date": "2018-10-30",
"values": 6583556
},
{
"date": "2018-10-31",
"values": 6584098
},
{
"date": "2018-11-01",
"values": 6584660
}
]
My chart looks smushed and I want my Y-Axis to start with the lowest value. Which in this case is 6574406 and want to increment by 25 with 10 ticks. I have tried many variations but am not able to get the x-axis un-overlapped. I tried to skew the values but that did not work either.
My chart:

Always Show Tooltip Regardless of Mouseover

Im new to JS and HTML so I am having a hard time implementing this change:
I want the values of the stacked bar graph to always be displayed rather than being dependent of the mouseover.
How would I go about changing the below code to achieve this?
var margin = {top: 20, right: 160, bottom: 35, left: 30};
var width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("body")
.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 + ")");
/* Data in strings like it would be if imported from a csv */
var data = [
{ year: "2006", redDelicious: "10", mcintosh: "15", oranges: "9", pears: "6" },
{ year: "2007", redDelicious: "12", mcintosh: "18", oranges: "9", pears: "4" },
{ year: "2008", redDelicious: "05", mcintosh: "20", oranges: "8", pears: "2" },
{ year: "2009", redDelicious: "01", mcintosh: "15", oranges: "5", pears: "4" },
{ year: "2010", redDelicious: "02", mcintosh: "10", oranges: "4", pears: "2" },
{ year: "2011", redDelicious: "03", mcintosh: "12", oranges: "6", pears: "3" },
{ year: "2012", redDelicious: "04", mcintosh: "15", oranges: "8", pears: "1" },
{ year: "2013", redDelicious: "06", mcintosh: "11", oranges: "9", pears: "4" },
{ year: "2014", redDelicious: "10", mcintosh: "13", oranges: "9", pears: "5" },
{ year: "2015", redDelicious: "16", mcintosh: "19", oranges: "6", pears: "9" },
{ year: "2016", redDelicious: "19", mcintosh: "17", oranges: "5", pears: "7" },
];
var parse = d3.time.format("%Y").parse;
// Transpose the data into layers
var dataset = d3.layout.stack()(["redDelicious", "mcintosh", "oranges", "pears"].map(function(fruit) {
return data.map(function(d) {
return {x: parse(d.year), y: +d[fruit]};
});
}));
// Set x, y and colors
var x = d3.scale.ordinal()
.domain(dataset[0].map(function(d) { return d.x; }))
.rangeRoundBands([10, width-10], 0.02);
var y = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d3.max(d, function(d) { return d.y0 + d.y; }); })])
.range([height, 0]);
var colors = ["b33040", "#d25c4d", "#f2b447", "#d9d574"];
// Define and draw axes
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.tickSize(-width, 0, 0)
.tickFormat( function(d) { return d } );
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%Y"));
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Create groups for each series, rects for each segment
var groups = svg.selectAll("g.cost")
.data(dataset)
.enter().append("g")
.attr("class", "cost")
.style("fill", function(d, i) { return colors[i]; });
var rect = groups.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y0 + d.y); })
.attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
.attr("width", x.rangeBand())
.on("mouseover", function() { tooltip.style("display", null); })
.on("mouseout", function() { tooltip.style("display", "none"); })
.on("mousemove", function(d) {
var xPosition = d3.mouse(this)[0] - 15;
var yPosition = d3.mouse(this)[1] - 25;
tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
tooltip.select("text").text(d.y);
});
// Draw legend
var legend = svg.selectAll(".legend")
.data(colors)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(30," + i * 19 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d, i) {return colors.slice().reverse()[i];});
legend.append("text")
.attr("x", width + 5)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d, i) {
switch (i) {
case 0: return "Anjou pears";
case 1: return "Naval oranges";
case 2: return "McIntosh apples";
case 3: return "Red Delicious apples";
}
});
// Prep the tooltip bits, initial display is hidden
var tooltip = svg.append("g")
.attr("class", "tooltip")
.style("display", "none");
tooltip.append("rect")
.attr("width", 30)
.attr("height", 20)
.attr("fill", "white")
.style("opacity", 0.5);
tooltip.append("text")
.attr("x", 15)
.attr("dy", "1.2em")
.style("text-anchor", "middle")
.attr("font-size", "12px")
.attr("font-weight", "bold");
svg {
font: 10px sans-serif;
shape-rendering: crispEdges;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
}
path.domain {
stroke: none;
}
.y .tick line {
stroke: #ddd;
}
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
I guess you need to add a label (tooltip) for each rect result. With little calculations I've got this:
http://jsfiddle.net/5o4jefap/87/
Here is the modified part without mouse events attached:
var rect = groups.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("g")
.attr('class', 'tooltip')
.append('rect')
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y0 + d.y); })
.attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
.attr("width", x.rangeBand())
groups.selectAll('.tooltip')
.append("rect")
.attr('width', 15)
.attr('height', 15)
.attr('fill', 'white')
.style("opacity", 0.5)
.attr('y', function(d) {
var rectY = y(d.y0 + d.y),
rectHalfHeight = ((y(d.y0) - y(d.y0 + d.y)) / 2),
tooltipHalfHeight = (this.getBBox().height / 2);
return rectY + rectHalfHeight - tooltipHalfHeight;
})
.attr("x", function(d) {
var rectX = x(d.x),
rectHalfWidth = (x.rangeBand() / 2),
tooltipHalfWidth = (this.getBBox().width / 2);
return rectX + rectHalfWidth - tooltipHalfWidth;
})
groups.selectAll('.tooltip')
.append('text')
.attr("x", function(d) {
var rectX = x(d.x),
rectHalfWidth = (x.rangeBand() / 2),
tooltipHalfWidth = (this.getBBox().width / 2);
return rectX + rectHalfWidth - tooltipHalfWidth;
})
.attr("y", function(d) {
var rectY = y(d.y0 + d.y),
rectHalfHeight = ((y(d.y0) - y(d.y0 + d.y)) / 2),
tooltipHalfHeight = (this.getBBox().height / 2);
return rectY + rectHalfHeight - tooltipHalfHeight;
})
.attr("dy", '5px')
.style("text-anchor", "middle")
.attr("font-size", "12px")
.attr("font-weight", "bold")
.text(function (data) {
return data.y;
} );

Donut bubble chart in D3.js version 3

I need to plot bubble chart, where each bubble is a donut chart like in below image in d3 version 3. I am able to achieve something, but don't understand how to distribute the circles horizontally, as my widget will be rectangular.
Also, how to make the donut bubble like in the image below. Any help would be appreciated. Thanks.
Code:
let colorCircles = {
'a': '#59bcf9',
'b': '#faabab',
'd': '#ffde85'
};
let tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip-inner")
.style("position", "absolute")
.style("min-width", "12rem")
.style("visibility", "hidden")
.style("color", "#627386")
.style("padding", "15px")
.style("stroke", '#b8bfca')
.style("fill", "none")
.style("stroke-width", 1)
.style("background-color", "#fff")
.style("border-radius", "6px")
.style("text-align", "center")
.text("");
let bubble = d3.layout.pack()
.sort(null)
.size([width, diameter])
.padding(15)
.value(function(d) {
return d[columnForRadius];
});
let svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", diameter)
.attr("class", "bubble");
let nodes = bubble.nodes({
children: dataset
}).filter(function(d) {
return !d.children;
});
let circles = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", function(d) {
return d.r;
})
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y - 20;
})
.style("fill", function(d) {
return colorCircles[d[columnForColors]]
})
.on("mouseover", function(d) {
tooltip.style("visibility", "visible");
tooltip.html('<p>' + d[columnForColors] + ": " + d[columnForText] + "</p><div class='font-bold displayInlineBlock'> $" + d[columnForRadius] + '</div>');
})
.on("mousemove", function() {
return tooltip.style("top", (d3.event.offsetY - 10) + "px").style("left", (d3.event.offsetX + 10) + "px");
})
// .on("mouseout", function() {
// return tooltip.style("visibility", "hidden");
// })
.attr("class", "node");
circles.transition()
.duration(1000)
.attr("r", function(d) {
return d.r;
})
.each('end', function() {
display_text();
});
function display_text() {
let text = svg
.selectAll(".text")
.data(nodes, function(d) {
return d[columnForText];
});
text.enter().append("text")
.attr("class", "graphText")
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y - 20;
})
.attr("dy", ".2em")
.attr("fill", "white")
.attr("font-size", function(d) {
return d.r / 5;
})
.attr("text-anchor", "middle")
.text(function(d) {
console.log(d)
return d[columnForText].substring(0, d.r / 3);
});
text.enter().append("text")
.attr("class", "graphText")
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y - 20;
})
.attr("dy", "1.3em")
.style("text-anchor", "middle")
.text(function(d) {
return '$' + d[columnForRadius];
})
.attr("font-size", function(d) {
return d.r / 5;
})
.attr("fill", "white");
}
function hide_text() {
let text = svg.selectAll(".text").remove();
}
d3.select(self.frameElement)
.style("height", diameter + "px");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script type="text/javascript">
var dataset = [
{ "Name": "Olives", "Count": 4319, "Category": "d" },
{ "Name": "Tea", "Count": 4159, "Category": "d" },
{ "Name": "Boiled Potatoes", "Count": 2074, "Category": "a" },
{ "Name": "Milk", "Count": 1894, "Category": "a" },
{ "Name": "Chicken Salad", "Count": 1809, "Category": "a" },
{ "Name": "Lettuce Salad", "Count": 1566, "Category": "a" },
{ "Name": "Lobster Salad", "Count": 1511, "Category": "a" },
{ "Name": "Chocolate", "Count": 1489, "Category": "b" }
];
var width = 300, diameter = 300;
var columnForText = 'Name',
columnForColors = 'Category',
columnForRadius = "Count";
</script>
Here's my fiddle: http://jsfiddle.net/71s86zL7/
I created a compound bubble pie chart and specified the inner radius in the pie chart.
var arc = d3.svg.arc()
.innerRadius(radius)
.outerRadius(radius);
.attr("d", function(d) {
arc.innerRadius(d.r+5);
arc.outerRadius(d.r);
return arc(d);
})
please let me know if there's any alternative solution to this problem.
I have a sorta hacky solution for this. What I did was:
to use the d3.layout.pie to get the startAngles and endAngles for arcs and create the arcs on top of the circles.
Give the circles a stroke line creating an effect of a donut chart.
And then I just had to adjust the startAngles and the endAngles so that all the arcs start from the same position.
Here's the fiddle:
let colorCircles = {
'a': '#59bcf9',
'b': '#faabab',
'd': '#ffde85'
};
let tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip-inner")
.style("position", "absolute")
.style("min-width", "12rem")
.style("visibility", "hidden")
.style("color", "#627386")
.style("padding", "15px")
.style("stroke", '#b8bfca')
.style("fill", "none")
.style("stroke-width", 1)
.style("background-color", "#fff")
.style("border-radius", "6px")
.style("text-align", "center")
.text("");
let bubble = d3.layout.pack()
.sort(null)
.size([width, diameter])
.padding(15)
.value(function(d) {
return d[columnForRadius];
});
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.Count;
});
var arc = d3.svg.arc()
let svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", diameter)
.attr("class", "bubble");
let nodes = bubble.nodes({
children: dataset
}).filter(function(d) {
return !d.children;
});
let g = svg.append('g')
let circles = g.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", function(d) {
return d.r;
})
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y - 20;
})
.style("fill", function(d) {
return colorCircles[d[columnForColors]]
})
.attr("class", "node")
.on("mouseover", function(d) {
tooltip.style("visibility", "visible");
tooltip.html('<p>' + d[columnForColors] + ": " + d[columnForText] + "</p><div class='font-bold displayInlineBlock'> $" + d[columnForRadius] + '</div>');
})
.on("mousemove", function() {
return tooltip.style("top", (d3.event.offsetY - 10) + "px").style("left", (d3.event.offsetX + 10) + "px");
})
.on("mouseout", function() {
return tooltip.style("visibility", "hidden");
});
arcs = g.selectAll(".arc")
.data(pie(dataset))
.enter().append("g")
.attr("class", "arc");
arcs.append("path")
.attr('transform', function(d) {
return 'translate(' + d['data']['x'] + ',' + (d['data']['y'] - 20) + ')';
})
.attr("d", function(d) {
return arc({
startAngle: 0,
endAngle: d.startAngle - d.endAngle,
innerRadius: d['data']['r'] - 2,
outerRadius: d['data']['r'] + 2,
})
}).on("mouseover", function(d) {
tooltip.style("visibility", "visible");
tooltip.html('<p>' + d['data'][columnForColors] + ": " + d['data'][columnForText] + "</p><div class='font-bold displayInlineBlock'> $" + d['data'][columnForRadius] + '</div>');
})
.on("mousemove", function() {
return tooltip.style("top", (d3.event.offsetY - 10) + "px").style("left", (d3.event.offsetX + 10) + "px");
})
.on("mouseout", function() {
return tooltip.style("visibility", "hidden");
});
circles.transition()
.duration(1000)
.attr("r", function(d) {
return d.r;
})
.each('end', function() {
display_text();
});
function display_text() {
let text = svg
.selectAll(".text")
.data(nodes, function(d) {
return d[columnForText];
});
text.enter().append("text")
.attr("class", "graphText")
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y - 20;
})
.attr("dy", ".2em")
.attr("fill", "white")
.attr("font-size", function(d) {
return d.r / 3;
})
.attr("text-anchor", "middle")
.text(function(d) {
return d[columnForText].substring(0, d.r / 3);
});
text.enter().append("text")
.attr("class", "graphText")
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y - 20;
})
.attr("dy", "1.3em")
.style("text-anchor", "middle")
.text(function(d) {
return '$' + d[columnForRadius];
})
.attr("font-size", function(d) {
return d.r / 5;
})
.attr("fill", "white");
}
function hide_text() {
let text = svg.selectAll(".text").remove();
}
d3.select(self.frameElement)
.style("height", diameter + "px");
path {
fill: orange;
stroke-width: 1px;
stroke: crimson;
}
path:hover {
fill: yellow;
}
circle {
fill: white;
stroke: slategray;
stroke-width: 4px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.13/d3.min.js"></script>
<script type="text/javascript">
var dataset = [{
"Name": "Olives",
"Count": 4319,
"Category": "d"
},
{
"Name": "Tea",
"Count": 4159,
"Category": "d"
},
{
"Name": "Boiled Potatoes",
"Count": 2074,
"Category": "a"
},
{
"Name": "Milk",
"Count": 1894,
"Category": "a"
},
{
"Name": "Chicken Salad",
"Count": 1809,
"Category": "a"
},
{
"Name": "Lettuce Salad",
"Count": 1566,
"Category": "a"
},
{
"Name": "Lobster Salad",
"Count": 1511,
"Category": "a"
},
{
"Name": "Chocolate",
"Count": 1489,
"Category": "b"
}
];
var width = 300,
diameter = 300;
var columnForText = 'Name',
columnForColors = 'Category',
columnForRadius = "Count";
</script>

d3 error displaying datetime on x-axis line chart

I am implementing a multi-line series chart using d3.js and I am getting an error pointing to my x-axis when trying to plot my dateTime from the data coming in. "Error: attribute d: Expected number, "MNaN,376.88020650…"."
Here is my function
var data = [{
"Brand": "Toyota",
"Count": 1800,
"Time": "2017-04-02 16"},
{
"Brand": "Toyota",
"Count": 1172,
"Time": "2017-04-02 17"},
{
"Brand": "Toyota",
"Count": 2000,
"Time": "2017-04-02 18"},
{
"Brand": "Honda",
"Count": 8765,
"Time": "2017-04-02 16"},
{
"Brand": "Honda",
"Count": 3445,
"Time": "2017-04-02 17"},
{
"Brand": "Honda",
"Count": 1232,
"Time": "2017-04-02 18"}
]
var dataGroup = d3.nest() //d3 method that groups data by Brand
.key(function(d) {return d.Brand;})
.entries(data);
console.log(JSON.stringify(dataGroup));
//var color = d3.scale.category10();
var vis = d3.select("#visualisation"),
WIDTH = 1000,
HEIGHT = 500,
MARGINS = {
top: 50,
right: 20,
bottom: 50,
left: 50
},
xScale = d3.scaleLinear().range([MARGINS.left, WIDTH - MARGINS.right]).domain([d3.min(data, function(d) { //set up x-axis based on data
return d.Time;
}), d3.max(data, function(d) {
return d.Time;
})]),
yScale = d3.scaleLinear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([d3.min(data, function(d) { //set up y-axis based on data
return d.Count;
}), d3.max(data, function(d) {
return d.Count;
})]),
xAxis = d3.axisBottom()
.scale(xScale),
yAxis = d3.axisLeft()
.scale(yScale)
vis.append("svg:g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")")
.call(xAxis);
vis.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(" + (MARGINS.left) + ",0)")
.call(yAxis);
var lineGen = d3.line()
.x(function(d) {
return xScale(d.Time);
})
.y(function(d) {
return yScale(d.Count);
})
.curve(d3.curveBasis);
dataGroup.forEach(function(d,i) { //iterate over the dataGroup and create line graph for each brand
vis.append('svg:path')
.attr('d', lineGen(d.values))
.attr('stroke', function(d,j) {
return "hsl(" + Math.random() * 360 + ",100%,50%)"; //random color for each brand line on graph
})
.attr('stroke-width', 2)
.attr('id', 'line_'+d.key)
.attr('fill', 'none');
lSpace = WIDTH/dataGroup.length; //define the legend space based on number of brands
vis.append("text")
.attr("x", (lSpace/2)+i*lSpace)
.attr("y", HEIGHT)
.style("fill", "black")
.attr("class","legend")
.on('click',function(){
var active = d.active ? false : true;
var opacity = active ? 0 : 1;
d3.select("#line_" + d.key).style("opacity", opacity);
d.active = active;
})
.text(d.key);
});
My dates are in yyyy-mm-dd HH format and what I am trying to accomplish is this for example:
"Time": "2017-04-02 16" converted to 'April 02' on the x axis and have the hour (HH) just displayed as a tool tip...etc
Here is a jsfiddle link https://jsfiddle.net/rsov2s2s/
Any help is appreciated.
In your data objects, Time is only a string. Thus, you`ll have to parse it into an actual date:
data.forEach(function(d){
d.Time = d3.timeParse("%Y-%m-%d %H")(d.Time)
});
In this function, d3.timeParse uses "%Y-%m-%d %H" as a specifier, which matches the structure of your strings.
After that, don't forget to change the xScale from scaleLinear to scaleTime.
Here is your code with those changes only:
var data = [{
"Brand": "Toyota",
"Count": 1800,
"Time": "2017-04-02 16"
}, {
"Brand": "Toyota",
"Count": 1172,
"Time": "2017-04-02 17"
}, {
"Brand": "Toyota",
"Count": 2000,
"Time": "2017-04-02 18"
}, {
"Brand": "Honda",
"Count": 8765,
"Time": "2017-04-02 16"
}, {
"Brand": "Honda",
"Count": 3445,
"Time": "2017-04-02 17"
}, {
"Brand": "Honda",
"Count": 1232,
"Time": "2017-04-02 18"
}];
data.forEach(function(d) {
d.Time = d3.timeParse("%Y-%m-%d %H")(d.Time)
});
var dataGroup = d3.nest() //d3 method that groups data by Brand
.key(function(d) {
return d.Brand;
})
.entries(data);
//var color = d3.scale.category10();
var vis = d3.select("#visualisation"),
WIDTH = 1000,
HEIGHT = 500,
MARGINS = {
top: 50,
right: 20,
bottom: 50,
left: 50
},
xScale = d3.scaleTime().range([MARGINS.left, WIDTH - MARGINS.right]).domain([d3.min(data, function(d) { //set up x-axis based on data
return d.Time;
}), d3.max(data, function(d) {
return d.Time;
})]),
yScale = d3.scaleLinear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([d3.min(data, function(d) { //set up y-axis based on data
return d.Count;
}), d3.max(data, function(d) {
return d.Count;
})]),
xAxis = d3.axisBottom()
.scale(xScale),
yAxis = d3.axisLeft()
.scale(yScale)
vis.append("svg:g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")")
.call(xAxis);
vis.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(" + (MARGINS.left) + ",0)")
.call(yAxis);
var lineGen = d3.line()
.x(function(d) {
return xScale(d.Time);
})
.y(function(d) {
return yScale(d.Count);
})
.curve(d3.curveBasis);
dataGroup.forEach(function(d, i) { //iterate over the dataGroup and create line graph for each brand
vis.append('svg:path')
.attr('d', lineGen(d.values))
.attr('stroke', function(d, j) {
return "hsl(" + Math.random() * 360 + ",100%,50%)"; //random color for each brand line on graph
})
.attr('stroke-width', 2)
.attr('id', 'line_' + d.key)
.attr('fill', 'none');
lSpace = WIDTH / dataGroup.length; //define the legend space based on number of brands
vis.append("text")
.attr("x", (lSpace / 2) + i * lSpace)
.attr("y", HEIGHT)
.style("fill", "black")
.attr("class", "legend")
.on('click', function() {
var active = d.active ? false : true;
var opacity = active ? 0 : 1;
d3.select("#line_" + d.key).style("opacity", opacity);
d.active = active;
})
.text(d.key);
});
.axis path {
fill: none;
stroke: #777;
shape-rendering: crispEdges;
}
.axis text {
font-family: Lato;
font-size: 13px;
}
.legend {
font-size: 14px;
font-weight: bold;
cursor: pointer;
<title>D3 Test</title>
<script src="https://d3js.org/d3.v4.js"></script>
<body>
<svg id="visualisation" width="1000" height="600"></svg>
<script src="InitChart.js"></script>
</body>

Categories