Display more data in d3 v4 grouped bar chart - javascript

I am using d3 V4 with angular4 to develop grouped bar chart. Here is my code.
imports;
let d3: any = D3;
#Component({
selector: 'grouped-bar-chart',
template: '<ng-content></ng-content>'
})
export class GroupedBarChartComponent {
#Input('chartConfig') config: GroupedBarChartConfigModel;
htmlElement: HTMLElement;
host;
width;
height;
innerWidth;
innerHeight;
margin;
x0Scale;
x1Scale;
yScale;
zScale; // color scale
svg;
tooltip;
breakPoint; // used to check if we are on small screens or not
keys;
dataset;
disabledSeries;
constructor(private element: ElementRef) {
if (this.config == null) {
this.config = new GroupedBarChartConfigModel();
}
this.htmlElement = this.element.nativeElement;
this.host = d3.select(this.element.nativeElement);
this.innerWidth = window.innerWidth;
this.innerHeight = window.innerHeight;
this.breakPoint = 768;
this.keys = []; // this holds bars contains in one cluster
this.disabledSeries = [];
this.margin = this.config.margin;
this.ngOnChanges();
}
#HostListener('window:resize', ['$event'])
onResize(event) {
this.ngOnChanges();
}
/**
* Every time the #Input is updated, rebuild the chart
**/
ngOnChanges(): void {
if (!this.config || !this.host || this.config.dataset.length === 0) return;
this.init();
this.setup();
this.buildSVG();
this.scaleAxises();
this.drawXAxis();
this.drawYAxis();
this.populate();
this.drawLegend();
}
/**
* Initialize chart dataset
*/
init(): void {
let data;
this.dataset = data;
}
/**
* Basically we get the window size and build the container configs
* also we create the xScale, yScale & zScale(color scale) ranges depending on calculations
**/
setup(): void {
this.keys = [];
this.width = this.config.width - this.margin.left - this.margin.right;
this.height = this.config.height - this.margin.top - this.margin.bottom;
// determine the geometry of the bars and spaces the individual bars within a cluster
this.x0Scale = d3.scaleBand().rangeRound([0, this.width]).paddingInner(0.01);
// spaces the different clusters of bars across the page
this.x1Scale = d3.scaleBand().padding(0.05);
this.yScale = d3.scaleLinear().rangeRound([this.height, 0]);
this.zScale = d3.scaleOrdinal()
.range(GraphColorPalette.PALETTE);
this.dataset[0].values.map((d: any) => {
if (this.keys.indexOf(d.key) === -1) {
this.keys.push(d.key);
}
});
}
/**
* build SVG element using configurations
**/
buildSVG(): void {
this.host.html('');
this.svg = this.host.append('svg')
.attr('width', this.width + this.margin.left + this.margin.right)
.attr('height', this.height + this.margin.top + this.margin.bottom)
.append('g')
.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
this.svg.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', this.width)
.attr('height', this.height)
.style('fill', '#eee')
.append('g')
.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
}
/**
**/
scaleAxises(): void {
this.x0Scale.domain(this.dataset.map((d: any) => {
return d.key;
}));
this.x1Scale.domain(this.keys).rangeRound([0, this.x0Scale.bandwidth()]);
let maxDomain = d3.max(this.dataset, (d: any) => {
return d3.max(this.keys, (key: any, i: number) => {
return d.values[i].value;
})
});
this.yScale.domain([0, maxDomain + 1]).nice();
}
/**
* Populate the chart using
**/
populate(): void {
this.drawColumns(this.x1Scale, this.keys, this.dataset);
this.createToolTips();
// add a title to the graph
this.svg.append("text")
.attr("x", (this.width / 2))
.attr("y", 0 - (this.margin.top / 2))
.attr("text-anchor", this.config.titlePosition)
.attr("class", "title")
.style("font-size", "18px")
.style("text-decoration", "underline")
.text(this.config.title);
}
drawColumns(x1Scale: any, keys: Array<string>, data: any): void {
this.svg.append("g")
.attr("class", "chart-layer")
.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", (d: any) => {
return "translate(" + this.x0Scale(d.key) + ",0)";
})
.selectAll("rect")
.data((d: any) => {
return keys.map((key, i) => {
return {id: d.key, key: key, name: d.name, value: d.values[i].value};
});
})
.enter().append("rect")
.attr("fill", (d) => {
return this.zScale(d.key);
})
.attr("class", "bar")
.style('cursor', 'pointer')
.on("mousemove", (d: any) => {
this.showTooltip(d);
})
.on("mouseout", () => {
this.hideTooltip();
})
.attr("x", (d: any) => {
return x1Scale(d.key);
})
.attr("y", this.height)
.attr("width", x1Scale.bandwidth())
.transition()
.ease(d3.easeLinear)
.duration(1000)
.delay((d, i) => {
return i * 50;
})
.attr("y", (d) => {
return this.yScale(d.value);
})
.attr("height", (d) => {
return Math.abs(this.height - this.yScale(d.value));
})
}
createToolTips(): void {
// Define the div for the tooltip
this.tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip");
}
showTooltip(d: any) {
// Show tooltip
this.tooltip.transition()
.duration(200)
.style("display", "inline");
this.tooltip
.html(d.id + "<br>" + d.name + "<br>" + d.key + ": " + d.value)
.style("left", d3.event.pageX + 10 + "px")
.style("top", d3.event.pageY - 25 + "px")
.style("position", "absolute")
.style("width", "auto")
.style("height", "auto")
.style("border", "0 none")
.style("border-radius", "8px")
.style("border-shadow", "-3px 3px 15px #888888")
.style("padding", "5px")
.style("text-align", "center")
.style("font", "12px sans-serif")
.style("background", "none repeat scroll 0 0 #fff")
.style('color', '#000');
}
hideTooltip() {
this.tooltip.transition()
.duration(500)
.style("display", "none");
}
/**
* Create X-axis
**/
drawXAxis(): void {
this.svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + this.height + ")")
.call(d3.axisBottom(this.x0Scale)
.tickSize(0)
.tickPadding(6));
// Add title to x axis
this.svg.append("text")
.attr("class", "axis-x--label")
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", this.config.xLabelPosition)
.attr("transform", () => {
switch (this.config.xLabelPosition) {
case ChartLabelPositions.START:
return "translate(" + (0) + "," + (this.height - (this.margin.bottom / 10) + 40) + ")";
case ChartLabelPositions.MIDDLE:
return "translate(" + (this.width / 2) + "," + (this.height - (this.margin.bottom / 10) + 40) + ")";
case ChartLabelPositions.END:
return "translate(" + (this.width) + "," + (this.height - (this.margin.bottom / 10) + 40) + ")";
default:
//do nothing
break;
}
}
)
.text(this.config.xLabel);
}
/**
* Create Y-axis
**/
drawYAxis(): void {
this.svg.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(this.yScale).tickSize(-this.width).ticks(null, "s"))
.append("text")
.attr("x", 2)
.attr("y", this.yScale(this.yScale.ticks().pop()) + 0.5)
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", this.config.yLabelPosition)
.text(this.config.yLabel);
}
}
I have more than 500 data to display in the chart. The problem is if there are more data then the bars are not showing. But if I hide some series on legend click then bars will be showing. How can I show more data in this graph.
Any suggestions are appreciated.
Thank You!

Related

Need to have only integers in x-axis - d3

I am facing issue in order to have integer values in x-axis(graph shown in above link). x-axis should be divided as 1,2,3 but it is getting divided as 0.0, 0.2, 0.4, 0.6,...,2.8 ,3.0 .
I have the following code:
private initSvg() {
d3.select("#svg1")
.attr("width", document.getElementById("svg1").parentElement.offsetWidth - 300 - this.margin.left - this.margin.right)
.attr("height", (document.getElementById("svg1").parentElement.offsetWidth / 3))
this.svg = d3.select('#svg1');
this.width = document.getElementById("svg1").parentElement.offsetWidth - 300 - this.margin.left - this.margin.right;
// this.height = +this.svg.attr("height") - this.margin.top - this.margin.bottom;
this.height = (document.getElementById("svg1").parentElement.offsetHeight - 100)
// console.log(this.width, this.height)
this.g = this.svg.append("g")
.attr("transform", "translate(" + (this.margin.left + 100) + "," + this.margin.top + ")");
}
private initAxis() {
this.y = d3Scale.scaleBand().rangeRound([0, this.height]).padding(0.1);
this.x = d3Scale.scaleLinear().rangeRound([this.width, 0]);
this.y.domain(this.STATISTICS.map((d) => d.qua));
this.x.domain([d3Array.max(this.STATISTICS, (d) => d.value + 2), 0]);
}
private drawAxis() {
this.g.append("g")
.attr("class", "x")
.attr("transform", "translate(0," + this.height + ")")
.call(d3Axis.axisBottom(this.x).tickSize(-this.height))
.selectAll("text")
.attr("dy", "1em")
.selectAll("path")
.attr("class", "opac")
.style("stroke-opacity", "0")
this.g.append("g")
.attr("class", "y")
// .attr("transform", "translate(0," + (- ((document.getElementById("svg1").parentElement.offsetWidth / 3) * 10) / 100) + ")")
.call(d3Axis.axisLeft(this.y).tickSize(-this.width))
.append("text")
.attr("class", "axis-title")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
}
private drawBars() {
var i = 0;
var trans = this.g.selectAll(".bar")
.data(this.STATISTICS)
.enter().append("rect")
.style("fill", (d: any) => { i++; return this.colorrange[i]; })
.attr("y", (d) => this.y(d.qua))
.attr("height", (this.y.bandwidth()))
// .attr("height", 60)
trans.transition()
.duration(2000)
.delay(function (d, i) {
return i * 50;
})
.attr("x", (d) => 0 /*this.x(d.AverageValue)*/)
.attr("width", (d) => /* this.height -*/ this.x((d.value == "" ? 0 : d.value)))
}
private drawLabel() {
this.g.selectAll('.label')
.data(this.STATISTICS)
.enter().append('text')
.attr('class', (d) => this.x(d.value) < 50 ? 'label bar-value-2' : 'label bar-value')
.style('font-size', (d) => (this.y.bandwidth() < 30 ? this.y.bandwidth() * 0.7 : 14) + 'px')
// .attr('x', (d) => this.x(d.AverageValue) < 40 ? 40 : this.x(d.AverageValue) - 7)
.attr('x', 100)
.attr('y', (d) => this.y(d.qua) + this.y.bandwidth() * 0.5 + 6);
}
And I am calling all this in a different method to get result:
setTimeout(() => {
this.initSvg()
this.initAxis()
this.drawAxis()
this.drawBars()
this.drawLabel()
}, 500);
Note:
STATISTICS = [
{qua: "Yes", value: 1}
{qua: "No", value: 1},
];
Tried to change something in x-domain but it didn't worked. Honestly, I am new to d3 and that's why don't know where to make changes.
Need further assistance.
Thanks.

No tooltip when hover the mouse over multiline chart

I'm using d3 as my charting library and after the graph plot as i hover my mouse i can't see any values at the datapoint but in the HTML console i can see event is getting triggered.
I want to see date and value when i hover but i'm not getting any idea how to proced with it as i'm very new to d3.
below is the small snippet i coded :
dataNest.forEach(function (d, i) {
svgVar.selectAll("path")
.data(dataNest)
.enter()
.append("path")
.attr("class", "line")
.style("stroke", function () { // Add the colours dynamically
return d.color = color(d.key);})
.on("mouseover", tooltip)
.attr("d", function(d) { return pricelineVar(d.values); })
.attr("fill","none");
function tooltip(d,i)
{
svgVar.style("left", d3.event.pageX + "px")
.style("top", d3.event.pageY + "px")
.style("display", "inline-block")
.html(" x:"+ d.date );
}
If anyone wants to give any suggestion i can paste the full code as well:
Full code
import { Component, OnInit, Input } from '#angular/core';
import * as d3 from "d3"
import * as d3Scale from 'd3-scale';
import * as d3Shape from 'd3-shape';
import * as d3Array from 'd3-array';
import * as d3Axis from 'd3-axis';
import {timeParse} from "d3-time-format";
#Component({
selector: 'app-d3graph',
template: `
<h2>{{subtitle}}</h2>
`
})
export class D3graphComponent implements OnInit {
#Input() storeIntraffic: string;
#Input() dateForD3: string;
#Input() peopleInSumStr: string;
title: string = 'D3.js with Angular 2!';
subtitle: string = 'Line Chart';
peopleInSumArr: any[];
private margin = {top: 20, right: 20, bottom: 30, left: 50};
private width: number;
private height: number;
private x: any;
private y: any;
private svg: any;
private priceline: any;
private line: d3Shape.Line<[number, number]>;
d3Data: any;
dashboard_date: any;
myArray: any[];
groupName: string;
data: any;
constructor() {
this.margin = { top: 30, right: 20, bottom: 70, left: 50 },
this.width = 1300 - this.margin.left - this.margin.right,
this.height = 800 - this.margin.top - this.margin.bottom;
}
ngOnInit() { }
ngAfterViewChecked() {
if (this.storeIntraffic !== undefined && typeof this.storeIntraffic === 'string') {
this.data = JSON.parse(this.peopleInSumStr);
this.peopleInSumArr = JSON.parse(this.peopleInSumStr);
this.dashboard_date = this.dateForD3;
// this.dashboard_date = this.dateForD3;
// console.log('d3 this.peopleInSumArr', this.peopleInSumStr);
// this.peopleInSumArr = JSON.parse(this.peopleInSumStr);
// console.log('d3 this.peopleInSumArr jajdjhdhjd', this.peopleInSumArr);
// console.log('this.dateForD3', this.dateForD3);
// this.storeIntraffic_plot();
this.initSvg();
this.initAxis();
this.drawAxis();
this.drawLine();
}
}
private initSvg() {
d3.select("svg").remove();
this.svg = d3.select("#svgcontainer")
.append("svg")
.attr("width", this.width + this.margin.left + this.margin.right)
.attr("height", this.height + this.margin.top + this.margin.bottom)
.append("g")
.attr("transform",
"translate(" + this.margin.left + "," + this.margin.top + ")")
.attr("stroke-width", 2);
}
private initAxis() {
// Parse the date / time
var parseDate = timeParse("%b %Y");
// Set the ranges
this.x = d3Scale.scaleTime().range([0, this.width]);
this.y = d3Scale.scaleLinear().range([this.height, 0]);
}
private drawAxis() {
var X = this.x;
var Y = this.y;
// Define the line
this.priceline = d3Shape.line()
.x(function (d) { return X(new Date(d.date)); })
.y(function (d) { return Y(d.peoplesum); });
}
private drawLine() {
var mindate = new Date(this.dashboard_date['startTime']),
maxdate = new Date(this.dashboard_date['endTime']);
this.x.domain(d3.extent([mindate, maxdate]));
// Scale the range of the data
var svgVar = this.svg;
var pricelineVar = this.priceline;
var margin = this.margin;
var height = this.height;
this.y.domain([0, d3.max(this.data, function (d) { return d.peoplesum; })]);
console.log("this.data", this.data);
// Nest the entries by symbol
var dataNest = d3.nest()
.key(function (d) { return d.storeid;})
.entries(this.data);
// set the colour scale
var color = d3.scaleOrdinal(d3.schemeCategory10);
var legendSpace = this.width / dataNest.length; // spacing for the legend
// Loop through each symbol / key
var tooltip = d3.select("body").append("div").style("display", "none");
function enableTooltip(d) {
tooltip.html("x:" + d.date)
.style("left", d3.event.pageX + "px")
.style("top", d3.event.pageY + "px")
.style("display", null);
}
dataNest.forEach(function (d, i) {
svgVar.selectAll("path")
.data(dataNest)
.enter()
.append("path")
.on("mouseover", enableTooltip)
.attr("d", function(d) { return pricelineVar(d.values); })
.attr("fill", "none");
// Add the Legend
svgVar.append("text")
.attr("x", (legendSpace / 2) + i * legendSpace) // space legend
.attr("y", height + (margin.bottom / 2) + 5)
.attr("class", "legend") // style the legend
.style("fill", function () { // Add the colours dynamically
return d.color = color(d.key);
})
.text(d.key)
.attr("stroke-width", 3)
});
console.log("after", dataNest);
// Add the X Axis
this.svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + this.height + ")")
.call(d3.axisBottom(this.x));
// Add the Y Axis
this.svg.append("g")
.attr("class", "axis")
.call(d3.axisLeft(this.y));
}
}
To get a tootltip during mouseover, first you have to define a tooltip with display as none.
var tooltip = d3.select("body").append("div").style("display", "none");
Then during mouseover event enable the tooltip and bind data to show in the tooltip.
function enableTooltip(d) {
tooltip.html("x:" + d.date)
.style("left", d3.event.pageX + "px")
.style("top", d3.event.pageY + "px")
.style("display", null);
}
You dont need forEach to iterate array. d3 data() takes care of that.
svgVar.selectAll("path")
.data(this.data)
.enter()
.append("path")
.on("mouseover", enableTooltip)
.attr("d", function(d) { return pricelineVar(d.values); })
.attr("fill", "none");
svgVar.selectAll("text")
.data(this.data)
.enter()
.append("text")
.attr("x", (legendSpace / 2) + i * legendSpace)
.attr("y", height + (margin.bottom / 2) + 5)
.attr("class", "legend")
.style("fill", function () {
return d.color = color(d.key);
})
.text(d.key)
.attr("stroke-width", 3);

Morph areas into bars with D3?

I'm able to generate the following graph using D3 areas:
I want to create the following animation. When the webpage loads, you see the first figure. Then, each of the areas morphs into a bar. Finally, I would like to allow users to toggle between the two figures by clicking "B" or "D".
I was able to add the buttons and the corresponding bars to my figure, but I'm having troubles figuring out how to do the animation. This is the code that I have right now:
HTMLWidgets.widget({
name: 'IMposterior',
type: 'output',
factory: function(el, width, height) {
// TODO: define shared variables for this instance
return {
renderValue: function(opts) {
console.log("threshold: ", opts.threshold);
console.log("bars: ", opts.bars);
var margin = {left:50,right:50,top:40,bottom:125};
xMax = d3.max(opts.data, function(d) { return d.x ; });
yMax = d3.max(opts.data, function(d) { return d.y ; });
xMin = d3.min(opts.data, function(d) { return d.x ; });
yMin = d3.min(opts.data, function(d) { return d.y ; });
var y = d3.scaleLinear()
.domain([0,yMax])
.range([height-margin.bottom,0]);
var x = d3.scaleLinear()
.domain([xMin,xMax])
.range([0,width]);
var yAxis = d3.axisLeft(y);
var xAxis = d3.axisBottom(x);
var area = d3.area()
.x(function(d){ return x(d.x) ;})
.y0(height-margin.bottom)
.y1(function(d){ return y(d.y); });
var svg = d3.select(el).append('svg').attr("height","100%").attr("width","100%");
var chartGroup = svg.append("g").attr("transform","translate("+margin.left+","+margin.top+")");
chartGroup.append("path")
.attr("d", area(opts.data.filter(function(d){ return d.x< -opts.MME ;})))
.style("fill", opts.colors[0]);
if(opts.MME !==0){
chartGroup.append("path")
.attr("d", area(opts.data.filter(function(d){ return (d.x < opts.MME & d.x > -opts.MME) ;})))
.style("fill", opts.colors[1]);
}
chartGroup.append("path")
.attr("d", area(opts.data.filter(function(d){ return d.x > opts.MME ;})))
.style("fill", opts.colors[2]);
chartGroup.append("g")
.attr("class","axis x")
.attr("transform","translate(0,"+(height-margin.bottom)+")")
.call(xAxis);
var tooltip = d3.tip()
.attr('class', 'd3-tip chart-data-tip')
.offset([30, 0])
.direction('s')
.html(function(d, i) {
return "<strong>" + d + "</strong> <span style='color:" + "white" + "'>"+ "</span>";
});
svg.call(tooltip);
chartGroup.selectAll("path").data(opts.text).on('mouseover', tooltip.show).on('mouseout', tooltip.hide);
// Bars
var yBar = d3.scaleLinear()
.domain([0,1])
.range([height-margin.bottom,0]);
var xBar = d3.scaleBand()
.domain(opts.bars.map(function(d) { return d.x; }))
.rangeRound([0, width]).padding(0.1);
var yAxisBar = d3.axisLeft(yBar);
var xAxisBar = d3.axisBottom(xBar);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
g.append("g")
.attr("class", "axis axis--x")
.attr("transform","translate(0,"+(height-margin.bottom)+")")
.call(d3.axisBottom(xBar));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(yBar).ticks(10, "%"))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Probability");
g.selectAll(".bar")
.data(opts.bars)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return xBar(d.x); })
.attr("y", function(d) { return yBar(d.y); })
.attr("width", xBar.bandwidth())
.style("fill", function(d) { return d.color; })
.attr("height", function(d) { return height - margin.bottom - yBar(d.y); });
// Add buttons
//container for all buttons
var allButtons= svg.append("g")
.attr("id","allButtons");
//fontawesome button labels
var labels= ["B", "D"];
//colors for different button states
var defaultColor= "#E0E0E0";
var hoverColor= "#808080";
var pressedColor= "#000000";
//groups for each button (which will hold a rect and text)
var buttonGroups= allButtons.selectAll("g.button")
.data(labels)
.enter()
.append("g")
.attr("class","button")
.style("cursor","pointer")
.on("click",function(d,i) {
updateButtonColors(d3.select(this), d3.select(this.parentNode));
d3.select("#numberToggle").text(i+1);
})
.on("mouseover", function() {
if (d3.select(this).select("rect").attr("fill") != pressedColor) {
d3.select(this)
.select("rect")
.attr("fill",hoverColor);
}
})
.on("mouseout", function() {
if (d3.select(this).select("rect").attr("fill") != pressedColor) {
d3.select(this)
.select("rect")
.attr("fill",defaultColor);
}
});
var bWidth= 40; //button width
var bHeight= 25; //button height
var bSpace= 10; //space between buttons
var x0= 20; //x offset
var y0= 10; //y offset
//adding a rect to each toggle button group
//rx and ry give the rect rounded corner
buttonGroups.append("rect")
.attr("class","buttonRect")
.attr("width",bWidth)
.attr("height",bHeight)
.attr("x",function(d,i) {return x0+(bWidth+bSpace)*i;})
.attr("y",y0)
.attr("rx",5) //rx and ry give the buttons rounded corners
.attr("ry",5)
.attr("fill",defaultColor);
//adding text to each toggle button group, centered
//within the toggle button rect
buttonGroups.append("text")
.attr("class","buttonText")
.attr("x",function(d,i) {
return x0 + (bWidth+bSpace)*i + bWidth/2;
})
.attr("y",y0+bHeight/2)
.attr("text-anchor","middle")
.attr("dominant-baseline","central")
.attr("fill","white")
.text(function(d) {return d;});
function updateButtonColors(button, parent) {
parent.selectAll("rect")
.attr("fill",defaultColor);
button.select("rect")
.attr("fill",pressedColor);
}
},
resize: function(width, height) {
// TODO: code to re-render the widget with a new size
}
};
}
});
And this is the figure that that code produces:
This does the trick:
HTMLWidgets.widget({
name: 'IMPosterior',
type: 'output',
factory: function(el, width, height) {
// TODO: define shared variables for this instance
return {
renderValue: function(opts) {
//transition
var transDuration = 1000;
var dataDiscrete = opts.bars.map((b, i) => {
b.y = Number(b.y);
b.desc = opts.text[i];
return b;
});
var distParams = {
min: d3.min(opts.data, d => d.x),
max: d3.max(opts.data, d => d.x)
};
distParams.cuts = [-opts.MME, opts.MME, distParams.max];
opts.data = opts.data.sort((a,b) => a.x - b.x);
var dataContinuousGroups = [];
distParams.cuts.forEach((c, i) => {
let data = opts.data.filter(d => {
if (i === 0) {
return d.x < c;
} else if (i === distParams.cuts.length - 1) {
return d.x > distParams.cuts[i - 1];
} else {
return d.x < c && d.x > distParams.cuts[i - 1];
}
});
data.unshift({x:data[0].x, y:0});
data.push({x:data[data.length - 1].x, y:0});
dataContinuousGroups.push({
color: opts.colors[i],
data: data
});
});
var margin = {
top: 50,
right: 20,
bottom: 80,
left: 70
},
dims = {
width: width - margin.left - margin.right,
height: height - margin.top - margin.bottom
};
var xContinuous = d3.scaleLinear()
.domain([distParams.min - 1, distParams.max + 1])
.range([0, dims.width]);
var xDiscrete = d3.scaleBand()
.domain(dataDiscrete.map(function(d) { return d.x; }))
.rangeRound([0, dims.width]).padding(0.1);
var y = d3.scaleLinear()
.domain([0, 1])
.range([dims.height, 0]);
var svg = d3.select(el).append("svg")
.attr("width", dims.width + margin.left + margin.right)
.attr("height", dims.height + margin.top + margin.bottom);
var g = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var xAxis = d3.axisBottom()
.scale(xDiscrete);
var yAxis = d3.axisLeft()
.scale(y)
.ticks(10)
.tickFormat(d3.format(".0%"));
var yLabel = g.append("text")
.attr("class", "y-axis-label")
.attr("transform", "rotate(-90)")
.attr("y", -52)
.attr("x", -160)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style("font-size", 14 + "px")
.text("Probability");
g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + dims.height + ")")
.call(xAxis);
g.append("g")
.attr("class", "y axis")
.call(yAxis);
var areas = g.selectAll(".area")
.data(dataDiscrete)
.enter().append("path")
.attr("class", "area")
.style("fill", function(d) { return d.color; })
.attr("d", function(d, i) {
let numPts = dataContinuousGroups[i].data.length - 2;
var path = d3.path()
path.moveTo(xDiscrete(d.x), y(0));
for (j=0; j<numPts; j++) {
path.lineTo(xDiscrete(d.x) + j*xDiscrete.bandwidth()/(numPts-1), y(d.y))
}
path.lineTo(xDiscrete(d.x) + xDiscrete.bandwidth(), y(0));
return path.toString();
});
var tooltip = d3.tip()
.attr('class', 'd3-tip chart-data-tip')
.offset([30, 0])
.direction('s')
.html(function(d, i) {
return "<span>" + dataDiscrete[i].desc + "</span>";
});
g.call(tooltip);
areas
.on('mouseover', tooltip.show)
.on('mouseout', tooltip.hide);
var thresholdLine = g.append("line")
.attr("stroke", "black")
.style("stroke-width", "1.5px")
.style("stroke-dasharray", "5,5")
.style("opacity", 1)
.attr("x1", 0)
.attr("y1", y(opts.threshold))
.attr("x2", dims.width)
.attr("y2", y(opts.threshold));
var updateXAxis = function(type, duration) {
if (type === "continuous") {
xAxis.scale(xContinuous);
} else {
xAxis.scale(xDiscrete);
}
d3.select(".x").transition().duration(duration).call(xAxis);
};
var updateYAxis = function(data, duration) {
var extent = d3.extent(data, function(d) {
return d.y;
});
extent[0] = 0;
extent[1] = extent[1] + 0.2*(extent[1] - extent[0]);
y.domain(extent);
d3.select(".y").transition().duration(duration).call(yAxis);
};
var toggle = function(to, duration) {
if (to === "distribution") {
updateYAxis(dataContinuousGroups[0].data.concat(dataContinuousGroups[1].data).concat(dataContinuousGroups[2].data), 0);
updateXAxis("continuous", duration);
areas
.data(dataContinuousGroups)
.transition()
.duration(duration)
.attr("d", function(d) {
var gen = d3.line()
.x(function(p) {
return xContinuous(p.x);
})
.y(function(p) {
return y(p.y);
});
return gen(d.data);
});
thresholdLine
.style("opacity", 0);
g.select(".y.axis")
.style("opacity", 0);
g.select(".y-axis-label")
.style("opacity", 0);
} else {
y.domain([0, 1]);
d3.select(".y").transition().duration(duration).call(yAxis);
updateXAxis("discrete", duration);
areas
.data(dataDiscrete)
.transition()
.duration(duration)
.attr("d", function(d, i) {
let numPts = dataContinuousGroups[i].data.length - 2;
var path = d3.path()
path.moveTo(xDiscrete(d.x), y(0));
for (j=0; j<numPts; j++) {
path.lineTo(xDiscrete(d.x) + j*xDiscrete.bandwidth()/(numPts-1), y(d.y))
}
path.lineTo(xDiscrete(d.x) + xDiscrete.bandwidth(), y(0));
return path.toString();
});
thresholdLine
.transition()
.duration(0)
.delay(duration)
.style("opacity", 1)
.attr("y1", y(opts.threshold))
.attr("y2", y(opts.threshold));
g.select(".y.axis")
.transition()
.duration(0)
.delay(duration)
.style("opacity", 1);
g.select(".y-axis-label")
.transition()
.duration(0)
.delay(duration)
.style("opacity", 1);
}
};
// Add buttons
//container for all buttons
var allButtons = svg.append("g")
.attr("id", "allButtons");
//fontawesome button labels
var labels = ["B", "D"];
//colors for different button states
var defaultColor = "#E0E0E0";
var hoverColor = "#808080";
var pressedColor = "#000000";
//groups for each button (which will hold a rect and text)
var buttonGroups = allButtons.selectAll("g.button")
.data(labels)
.enter()
.append("g")
.attr("class", "button")
.style("cursor", "pointer")
.on("click", function(d, i) {
updateButtonColors(d3.select(this), d3.select(this.parentNode));
d3.select("#numberToggle").text(i + 1);
if (d === "D") {
toggle("distribution", transDuration);
} else {
toggle("discrete", transDuration);
}
})
.on("mouseover", function() {
if (d3.select(this).select("rect").attr("fill") != pressedColor) {
d3.select(this)
.select("rect")
.attr("fill", hoverColor);
}
})
.on("mouseout", function() {
if (d3.select(this).select("rect").attr("fill") != pressedColor) {
d3.select(this)
.select("rect")
.attr("fill", defaultColor);
}
});
var bWidth = 40; //button width
var bHeight = 25; //button height
var bSpace = 10; //space between buttons
var x0 = 20; //x offset
var y0 = 10; //y offset
//adding a rect to each toggle button group
//rx and ry give the rect rounded corner
buttonGroups.append("rect")
.attr("class", "buttonRect")
.attr("width", bWidth)
.attr("height", bHeight)
.attr("x", function(d, i) {
return x0 + (bWidth + bSpace) * i;
})
.attr("y", y0)
.attr("rx", 5) //rx and ry give the buttons rounded corners
.attr("ry", 5)
.attr("fill", defaultColor);
//adding text to each toggle button group, centered
//within the toggle button rect
buttonGroups.append("text")
.attr("class", "buttonText")
.attr("x", function(d, i) {
return x0 + (bWidth + bSpace) * i + bWidth / 2;
})
.attr("y", y0 + bHeight / 2)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "central")
.attr("fill", "white")
.text(function(d) {
return d;
});
function updateButtonColors(button, parent) {
parent.selectAll("rect")
.attr("fill", defaultColor);
button.select("rect")
.attr("fill", pressedColor);
}
toggle("distribution", 0);
setTimeout(() => {
toggle("discrete", transDuration);
}, 1000);
},
resize: function(width, height) {
// TODO: code to re-render the widget with a new size
}
};
}
});

D3 v4 multi series line chart zooming issue

I'm trying to add a zoom ability to a multi series line chart I've built with d3 and Angular 2.
ngOnChanges(data):void {
this.setup(data);
this.initData(data);
this.buildSVG();
this.scaleAxis(data);
this.populate();
this.drawXAxis();
this.drawYAxis();
this.zoomEventHandler();
this.addVerticalLineTooltip();
}
private setup(data):void {
this.margin = {top: 50, right: 100, bottom: 100, left: 100};
this.width = 600;
this.height = 400;
this.xScale = d3.scaleTime().range([0, this.width]);
this.yScale = d3.scaleLinear().range([this.height, 0]);
this.zScale = d3.scaleOrdinal(d3.schemeCategory10);
this.zScale.domain(d3.keys(data[0]).filter(function (key) {
return key !== "date";
}));
}
private initData(data) {
// format the data
data.forEach((d)=> {
d.date = this.parseDate(d.date);
});
this.ds = this.zScale.domain().map((name)=> {
return {
name: name,
values: data.map((d) => {
return {x: d.date, y: d[name], name: name};
})
};
});
}
/**
* build SVG element using the configurations
**/
private buildSVG():void {
this.host.html('');
this.svg = this.host.append('svg')
.attr('width', this.width + this.margin.left + this.margin.right)
.attr('height', this.height + this.margin.top + this.margin.bottom)
.append('g')
.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
this.svg.append('rect')
.attr('class', 'view')
.attr('x', 0.5)
.attr('y', 0.5)
.attr('width', this.width)
.attr('height', this.height)
.style('fill', '#EEEEEE')
.style('stroke-width', '0px');
}
private scaleAxis(data) {
this.xScale.domain(d3.extent(data, (d) => {
return d.date;
}));
this.yScale.domain([
d3.min(this.ds, (c) => {
return d3.min(c.values, function (d) {
return d.y;
});
}),
d3.max(this.ds, (c) => {
return d3.max(c.values,
(d) => {
return d.y;
});
})
]);
this.zScale.domain(this.ds.map((c) => {
return c.name;
}));
this.make_x_axis = ()=> {
return d3.axisBottom()
.scale(this.xScale)
.tickPadding(15);
};
this.make_y_axis = ()=> {
return d3.axisLeft()
.scale(this.yScale)
.tickPadding(10);
};
}
/**
* Create x axis
**/
private drawXAxis():void {
this.xAxis = d3.axisBottom()
.scale(this.xScale)
.tickPadding(15);
this.svg.append('g')
.attr('class', 'x-axis')
.attr('transform', 'translate(0,' + this.height + ')')
.call(this.xAxis);
this.svg.append('text')
.attr('text-anchor', 'middle')
.attr('transform', 'translate(' + (this.width / 2) + "," + (this.height + this.margin.bottom / 2) + ")")
.text('Date');
}
/**
*create y axis
**/
private drawYAxis():void {
this.yAxis = d3.axisLeft()
.scale(this.yScale)
.tickPadding(10);
this.svg.append('g')
.attr('class', 'y-axis')
.call(this.yAxis)
.append('text')
.attr('transform', 'rotate(-90)');
this.svg.append('text')
.attr('text-anchor', 'middle')
.attr("transform", "translate(-" + (this.margin.left / 2) + "," + (this.height / 2) + ")rotate(-90)")
.text('Sales / Searches');
this.svg.append("g")
.attr("class", "y grid")
.call(this.make_y_axis()
.tickSize(-this.width, 0, 0)
.tickFormat(""))
.style("opacity", "0.1");
}
/**
* Populate the graphs
**/
private populate():void {
// define the line
this.line = d3.line()
.x((d) => {
return this.xScale(d.x);
})
.y((d) => {
return this.yScale(d.y);
});
let chartdata = this.svg.selectAll(".chartdata")
.data(this.ds)
.enter().append("g")
.attr("class", "chartdata");
chartdata.append("path")
.attr("class", "line")
.attr("d", (d) => {
return this.line(d.values);
})
.style("fill", "none")
.style("stroke-width", "2px")
.style("stroke", (d) => {
return this.zScale(d.name);
});
chartdata.append("text")
.datum((d)=> {
return {
name: d.name, value: d.values[d.values.length - 1]
};
})
.attr("transform", (d) => {
return "translate(" +
this.xScale(d.value.x) + "," + this.yScale(d.value.y) + ")";
})
.attr('class', 'lineLabel')
.attr("x", 3)
.attr("dy", "0.35em")
.style("font", "14px open-sans")
.text((d) => {
return d.name;
});
// add the dots
chartdata.selectAll(".circle")
.data((d) => {
return d.values;
})
.enter().append("circle")
.attr("class", "circle")
.attr("r", 4)
.attr("cx", (d) => {
return this.xScale(d.x);
})
.attr("cy", (d) => {
return this.yScale(d.y);
})
.style("fill", (d) => { // Add the colours dynamically
return this.zScale(d.name);
});
}
private zoomEventHandler() {
let zoom = d3.zoom()
.scaleExtent([1, 2])
.translateExtent([[0, -100], this.width + 90, this.height + 100]).on("zoom", () => this.zoomed());
this.svg.call(zoom);
}
private zoomed() {
d3.select('.x-axis').call(this.xAxis.scale(d3.event.transform.rescaleX(this.xScale)));
d3.selectAll('.line').attr('transform', 'translate(' + d3.event.transform.x + ',' + d3.event.transform.y + ') scale(' + d3.event.transform.k + ')');
d3.selectAll('.circle').attr('transform', 'translate(' + d3.event.transform.x + ',' + d3.event.transform.y + ') scale(' + d3.event.transform.k + ')');
}
My chart is responding to the mouse scroll event as below.
Any suggestions on what I have done wrong with my code?
Thank you !

set the gridline at the back of gantt when redraw in d3

i know this maybe super duper simple to you guyz but this is the last piece of my project and i am done so i already want to finish this. here is my problem when i first load my gantt chart i draw first the axis and the gridlines before the chart it self so it appears as i desire that the gridlines is the background of my chart but when i redraw my chart there is the problem exist the gantt draw first before the gridlines so the output is the gridlines is covering the gantt chart which looks so bad. i will post my codes so you can check it. I can't pin point where i put first the gridlines. I think i need and extra eye here..........
d3.gantt = function() {
var FIT_TIME_DOMAIN_MODE = "fit";
var FIXED_TIME_DOMAIN_MODE = "fixed";
var margin = {
top : 50,
right : 40,
bottom : 20,
left : 120
};
var timeDomainStart = d3.time.day.offset(new Date(),-3);
var timeDomainEnd = d3.time.hour.offset(new Date(),+3);
var timeDomainMode = FIT_TIME_DOMAIN_MODE;// fixed or fit
var taskTypes = [];
var height = 500 - margin.top - margin.bottom-5;
var width = 1200 - margin.right - margin.left-5;
var tickFormat = "%H:%M";
var keyFunction = function(d) {
return d.startDate + d.taskName + d.endDate;
};
var rectTransform = function(d) {
return "translate(" + x(d.startDate) + "," + y(d.taskName) + ")";
};
var x = d3.time.scale().domain([ timeDomainStart, timeDomainEnd ]).range([ 0, width ]).clamp(true);
var y = d3.scale.ordinal().domain(taskTypes).rangeRoundBands([ 0, height - margin.top - margin.bottom ], .1);
var xAxis = d3.svg.axis().scale(x).orient("bottom").tickFormat(d3.time.format(tickFormat)).tickSubdivide(true).tickSize(8).tickPadding(8);
var yAxis = d3.svg.axis().scale(y).orient("left").tickSize(0);
var initTimeDomain = function(tasks) {
if (timeDomainMode === FIT_TIME_DOMAIN_MODE) {
if (tasks === undefined || tasks.length < 1) {
timeDomainStart = d3.time.day.offset(new Date(), -3);
timeDomainEnd = d3.time.hour.offset(new Date(), +3);
return;
}
tasks.sort(function(a, b) {
return a.endDate - b.endDate;
});
timeDomainEnd = tasks[tasks.length - 1].endDate;
tasks.sort(function(a, b) {
return a.startDate - b.startDate;
});
timeDomainStart = tasks[0].startDate;
}
};
var xAxisGrid = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(-height + margin.top + margin.bottom, 0, 0)
.tickFormat("");
var initAxis = function() {
x = d3.time.scale().domain([ timeDomainStart, timeDomainEnd ]).range([ 0, width ]).clamp(true);
y = d3.scale.ordinal().domain(taskTypes).rangeRoundBands([ 0, height - margin.top - margin.bottom ], .1);
xAxis = d3.svg.axis().scale(x).orient("bottom").tickFormat(d3.time.format(tickFormat)).tickSubdivide(true)
.tickSize(8).tickPadding(8);
yAxis = d3.svg.axis().scale(y).orient("left").tickSize(0);
xAxisGrid = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(-height + margin.top + margin.bottom, 0, 0)
.tickFormat("");
};
/////////////////////////////////
//Creating the chart
////////////////////////////
function gantt(tasks) {
initTimeDomain(tasks);
initAxis();
var dateFormat = d3.time.format("%Y-%m-%d");
var svg = d3.select("#gantt_chart")
.append("svg")
.attr("class", "chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("class", "gantt-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", (height + margin.top + margin.bottom) / tasks[tasks.length - 1].endDate)
.attr("transform", "translate(" + margin.left + ", " + margin.top + ")");
var div = d3.select("body").append("div")
.attr("class","tooltip")
.style("opacity",0);
//this is the x-axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0, " + (height - margin.top - margin.bottom) + ")")
.transition()
.call(xAxis)
.selectAll("text")
.style("text-anchor","end")
.attr("dx", 35)
.attr("dy", 5);
//.attr("dx", "-.8em")
//.attr("dy", -10)
//.attr("transform", function(d){return "rotate(-90)"});
//this is the y-axis
svg.append("g").attr("class", "y axis").transition().call(yAxis);
// title of the gantt
svg.append("g")
.append("text")
.attr("x", 300)
.attr("y", -30)
.attr("class","title")
.style("font-size",20)
.text("MINI-PM SCHEDULER GANTT CHART");
// y title
svg.append("g")
.append("text")
.attr("transform", "rotate(-90)")
.attr("dx", -220)
.attr("dy", -100)
.style("font-size",16)
.text("HANDLER ID");
//this is the legend part
var colors = [["RUNNING", "#669900"],["WARNING", "#ffbb33"],["DOWN", "#FF0000"]];
var legend = svg.append("g")
.attr("class", "legend");
var circle = legend.selectAll('circle')
.data(colors)
.enter()
.append("circle")
.attr("cx", function (d, i) {
return (i * 80)+(width/2)-125;
})
.attr("cy", 405)
.attr("r", 5)
.style("fill", function (d) {
return d[1];
});
var legendText = legend.selectAll('text')
.data(colors)
.enter()
.append("text")
.attr("x", function (d, i) {
return (i * 80)+(width/2)-115;
})
.attr("y", 410)
.text(function (d) {
return d[0];
});
// Add X Axis grid lines
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0, " + (height - margin.top - margin.bottom) + ")")
.call(xAxisGrid);
//this is the actual gantt
svg.selectAll(".chart")
.data(tasks, keyFunction).enter()
.append("rect")
.attr("rx", 0)
.attr("ry", 0)
.attr("class", function(d){
if(d.status > 75)
{
return "bar-failed";
}
else if (d.status >= 55 && d.status <= 75){
return "bar-killed";
}
else{
return "bar-running";
}
})
.attr("y", 0)
.attr("transform", rectTransform)
.attr("height", function(d) { return y.rangeBand(); })
.attr("width", function(d) {
return (x(d.endDate) - x(d.startDate));
})
.on("mouseover", function(d){
div.transition()
.duration(200)
.style("opacity", .9);
div.html("HandlerID: " + d.taskName + "<br>" + "startDate: " + dateFormat(d.startDate) + "<br/>" + "endDate: " + dateFormat(d.endDate) + "<br/>" + "% Insertions: " + d3.round(d.status,2) + "%")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout",function(d){
div.transition()
.duration(500)
.style("opacity", 0);
});
/*
svg.append("g")
.append("text")
.attr("x", width / 2)
.attr("y", 380)
.style("font-size",12)
.text("PERIOD"); */
return gantt;
};
gantt.redraw = function(tasks) {
initTimeDomain(tasks);
initAxis();
var dateFormat = d3.time.format("%Y-%m-%d");
var div = d3.select("body").append("div")
.attr("class","tooltip")
.style("opacity",0);
var svg = d3.select("#gantt_chart");
var ganttChartGroup = svg.select(".gantt-chart");
var rect = ganttChartGroup.selectAll("rect").data(tasks, keyFunction);
rect.enter()
.insert("rect",":first-child")
.attr("rx", 0)
.attr("ry", 0)
.attr("class", function(d){
if(d.status > 75)
{
return "bar-failed";
}
else if (d.status >= 55 && d.status <= 75){
return "bar-killed";
}
else{
return "bar-running";
}
})
.transition()
.attr("y", 0)
.attr("transform", rectTransform)
.attr("height", function(d) { return y.rangeBand(); })
.attr("width", function(d) {
return (x(d.endDate) - x(d.startDate));
});
rect.transition()
.attr("transform", rectTransform)
.attr("height", function(d) { return y.rangeBand(); })
.attr("width", function(d) {
return (x(d.endDate) - x(d.startDate));
});
rect.exit().remove();
rect
.on("mouseover", function(d){
div.transition()
.duration(200)
.style("opacity", .9);
div.html("HandlerID: " + d.taskName + "<br>" + "startDate: " + dateFormat(d.startDate) + "<br/>" + "endDate: " + dateFormat(d.endDate) + "<br/>" + "% Insertions: " + d3.round(d.status,2) + "%")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout",function(d){
div.transition()
.duration(500)
.style("opacity", 0);
});
svg.select(".grid").transition().call(xAxisGrid);
svg.select(".x").transition().call(xAxis);
svg.select(".y").transition().call(yAxis);
return gantt;
}; // end of redraw
gantt.margin = function(value) {
if (!arguments.length)
return margin;
margin = value;
return gantt;
};
gantt.timeDomain = function(value) {
if (!arguments.length)
return [ timeDomainStart, timeDomainEnd ];
timeDomainStart = +value[0], timeDomainEnd = +value[1];
return gantt;
};
/**
* #param {string}
* vale The value can be "fit" - the domain fits the data or
* "fixed" - fixed domain.
*/
gantt.timeDomainMode = function(value) {
if (!arguments.length)
return timeDomainMode;
timeDomainMode = value;
return gantt;
};
gantt.taskTypes = function(value) {
if (!arguments.length)
return taskTypes;
taskTypes = value;
return gantt;
};
gantt.taskStatus = function(value) {
if (!arguments.length)
return taskStatus;
taskStatus = value;
return gantt;
};
gantt.width = function(value) {
if (!arguments.length)
return width;
width = +value;
return gantt;
};
gantt.height = function(value) {
if (!arguments.length)
return height;
height = +value;
return gantt;
};
gantt.tickFormat = function(value) {
if (!arguments.length)
return tickFormat;
tickFormat = value;
return gantt;
};
return gantt;
};

Categories

Resources