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);
Related
I am creating a chart using d3 js and I was trying to convert a DateTime string to time. but it is returning TypeError: Cannot read property 'timeFormat' of undefined
The bellow is the full file that Created.
import {BaseElement} from '../../core/base-element';
import {utilsMixin} from '../../core/mixins/utils-mixin';
// import {LitElement} from 'lit-element';
import * as d3 from 'd3';
export const AreaChartBase = class extends utilsMixin(dataSourceMixin(BaseElement)) {
constructor() {
super();
this.title = '';
this.chartTheme = {
margin : {
top: 35,
right: 145,
bottom: 35,
left: 45
},
barsColors : '#23d160',
tickColor : '#aeaeae',
pathColor : '#aeaeae',
gridColor : '#aeaeae',
barTitleColor : "#23d160",
}
}
static get is() {
return 'area-chart';
}
static get properties() {
return {
title: String,
chartTheme: Object,
}
}
timeFormat(str){
const dateTimeString = new Date(str);
const time = dateTimeString.getTime();
const normalTime = new Date(time).toLocaleDateString("en-US");
return normalTime;
}
areaChart(selector, props, data){
// initialize the variables
const {
margin,
width,
height,
barsColors,
tickColor,
pathColor,
gridColor,
barTitleColor
} = props;
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const svg = d3.select(selector);
let chart = svg.selectAll('svg')
.data([null]);
chart = chart.enter()
.append('svg')
.merge(svg)
// .attr('width', innerWidth)
// .attr('height', innerHeight);
.attr('viewBox', `0,0, ${innerWidth}, ${innerHeight}`)
chart.append('text')
.attr('x', 0)
.attr('y', margin.top / 2)
.attr('text-anchor', 'start')
.style('font-size', '18px')
.attr('fill', '#4c6072')
.text('Area chart');
let g = chart.selectAll('g').data([null]);
g = g.enter()
.append('g')
.merge(g)
.attr('transform', `translate(${margin.left}, ${margin.top})`);
// Add X axis --> it is a date format
const x = d3.scaleBand()
.rangeRound([0, innerWidth])
.padding(0.1);
const y = d3.scaleLinear()
.range([innerHeight - margin.bottom - 17, 0]);
x.domain(data.map(function (d) {
console.log(this.timeFormat(d[0]));
return this.timeFormat(d[0]);
}));
y.domain( d3.extent(data, function(d) { return +d[4]; }) );
const xAxis = d3.axisBottom(x);
const yAxis = d3.axisLeft(y)
.ticks(5)
.tickSizeInner(-innerWidth)
.tickSizeOuter(0)
.tickPadding(10);
let xAxisG = g.selectAll('.x axis').data([null]);
xAxisG = xAxisG.enter()
.append('g')
.attr('class', 'x axis')
.merge(xAxisG)
.attr('transform', `translate(0, ${innerHeight - margin.bottom - 17})`)
let yAxisG = g.selectAll('y axis').data([null]);
yAxisG = yAxisG.enter()
.append('g')
.attr('class', 'y axis')
.merge(yAxisG)
xAxisG.call(xAxis);
xAxisG.selectAll('.tick text')
.attr('fill', tickColor)
xAxisG.selectAll('.tick line')
.attr('stroke', pathColor);
xAxisG.select('.domain')
.attr('stroke', 'transparent');
yAxisG.call(yAxis);
yAxisG.selectAll('.tick text')
.attr('fill', tickColor)
yAxisG.selectAll('.tick line')
.attr('stroke', pathColor);
yAxisG.select('.domain')
.attr('stroke', 'transparent');
// append the svg object to the body of the page
// let svg = d3.select(selector)
// .append("svg")
// .attr("width", width + margin.left + margin.right)
// .attr("height", height + margin.top + margin.bottom)
// .append("g")
// .attr("transform",
// "translate(" + margin.left + "," + margin.top + ")");
// const x = d3.scaleBand()
// .domain(data.map(function (d) {
// return d[0];
// }))
// .range([ 0, width ]);
// svg.append("g")
// .attr("transform", "translate(0," + (height+5) + ")")
// .call(d3.axisBottom(x).ticks(5).tickSizeOuter(0));
// // Add Y axis
// var y = d3.scaleLinear()
// .domain( d3.extent(data, function(d) { return +d[4]; }) )
// .range([ height, 0 ]);
// svg.append("g")
// .attr("transform", "translate(-5,0)")
// .call(d3.axisLeft(y).tickSizeOuter(0));
// Add the area
svg.append("path")
.datum(data)
.attr("fill", "#69b3a2")
.attr("fill-opacity", .3)
.attr("stroke", "none")
.attr("d", d3.area()
.x(function(d) { return x(d[0]) })
.y0( height )
.y1(function(d) { return y(+d[4]) })
)
// Add the line
svg.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "#69b3a2")
.attr("stroke-width", 2)
.attr("d", d3.line()
.x(function(d) { return x(d[0]) })
.y(function(d) { return y(+d[4]) })
)
// Add the line
svg.selectAll("myCircles")
.data(data)
.enter()
.append("circle")
.attr("fill", "red")
.attr("stroke", "none")
.attr("cx", function(d) { return x(d[0]) })
.attr("cy", function(d) { return y(parseInt(d[4])) })
.attr("r", 2)
}
initAreaChart(dsc){
const rows = dsc.rows;
const data = dsc.data;
const cont = this.qs('#chart');
this.areaChart(cont, Object.assign({}, this.chartTheme, {
width: this.qs('#container').clientWidth,
height: 400,
}), rows);
}
firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
let self = this;
this.loader.then(dsc => {
self.initAreaChart(dsc);
});
}
dscDataName() {
return this.e.defaultValue;
}
init(pElement, loader) {
super.init(pElement, loader);
var self = this;
self.loader = this.loadData();
}
}```
if you guys can help me find a fix for the above error I will appreciate it. Thanks in advance
Try to use arrow function. Arrow functions keep context.
x.domain(data.map( (d) => {
console.log(this.timeFormat(d[0]));
return this.timeFormat(d[0]);
}));
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
I'm building a series of d3 graphs within angular 5. I'm graphing the same d3 multiple times with different data each time, so I'm assigning a variable to the id. The problem is, the id is assigned at the same time that 'd3.select()' is looking for it, therefore it hasn't been assigned yet and I get error Cannot read property 'getAttribute' of null
How can I assign the 'id' of my intended 'svg' a variable from the same data that I use to graph it? Right now it's all in an 'onInit' function in my component.
In my precipitation.component.ts:
this.input_id += this.data.id
var svg = d3.select("#"+this.input_id)
My html looks like:
<svg width="180" height="100"[attr.id]="input_id" > </svg>
The whole precipitation.component.ts for reference:
import { Component, OnInit, Input } from '#angular/core';
import { DataService } from '../data.service';
import { Http } from '#angular/http';
import * as d3 from 'd3';
#Component({
selector: 'app-precipitation',
templateUrl: './precipitation.component.html',
styleUrls: ['./precipitation.component.css']
})
export class PrecipitationComponent implements OnInit {
#Input() site1;
constructor(private _dataService: DataService, private http: Http) { }
date: Date;
dates: any;
value: Number;
eachobj: any;
data: any;
values: any;
average: any;
myClasses: any;
name: any;
input_id;
ngOnInit() {
this.name = this.site1.name;
var parse = d3.timeParse("%Y-%m-%d")
this.data = this.site1
this.input_id = ""
this.input_id += "precipitation"
this.input_id += this.data.id
this.dates = Object.keys(this.data.historical_data)
this.values = Object.values(this.data.historical_data)
this.values.forEach((obj, i) => obj.date = this.dates[i]);
this.data = this.values
var svg = d3.select("#"+this.input_id)
// svg = svg.select("." + this.name)
var margin = { top: 20, right: 20, bottom: 30, left: 0 },
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
barPadding = 5,
barWidth = (width / this.data.length);
var last = this.data[0].date
var first = this.data[(this.data.length) - 1].date
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1)
.domain(this.data.map(function (d) { return d['date']; }));
var y = d3.scaleLinear().rangeRound([height, 0])
.domain(<[Date, Date]>d3.extent(this.data, function (d) { return d['precipitation']; }));
g.selectAll(".bar")
.data(this.data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function (d) { return x(d['date']); })
.attr("y", function (d) { return y(d['precipitation']); })
.attr("width", barWidth - barPadding)
.attr("height", function (d) { return height - y(d['precipitation']); });
var line = d3.line()
.x(function (d) { return x(d['date']); })
.y(function (d) { return y(d['precipitation']); });
svg.append('g')
.attr("class", "axis--x")
.append("text")
.attr("fill", "#000")
.attr("x", (width / 2) - 80)
.attr("y", height + 40)
.text(first)
.style("font-size", "09")
.style("font-family", "Roboto")
.style('fill', '#5a5a5a');
svg.append('g')
.attr("class", "axis--x")
.append("text")
.attr("fill", "#000")
.attr("x", (width / 2) + 45)
.attr("y", height + 40)
.text(last)
.style("font-size", "09")
.style("font-family", "Roboto")
.style('fill', '#5a5a5a');
svg.append("path")
.data(this.data)
.attr("class", "line")
.attr("d", line);
}
}
By the way I receive the data through the parent component:
<app-precipitation [site1]="site"></app-precipitation>
I have a multi line graph and i want to have a tool tip on it so that as i hover my mouse over the line it shows the point on x and y axis.
I have my data somewhere in the below form and i'm refrencing the mouse event part from somewhere here
I have my data in the below format and i'm passing it as object in angular between component
{storeid: "", peoplesum: 137.14285714285714, date: "2018-06-02"}
{storeid: "", peoplesum: 139.28571428571428, date: "2018-06-03"}
{storeid: "", peoplesum: 123, date: "2018-06-04"}
{storeid: "", peoplesum: 144, date: "2018-06-05"}
{storeid: "", peoplesum: 150, date: "2018-06-06"}
{storeid: "", peoplesum: 154.28571428571428, date: "2018-06-07"}
{storeid: "", peoplesum: 159.85714285714286, date: "2018-06-08"}
{storeid: "", peoplesum: 145.71428571428572, date: "2018-06-09"}
{storeid: "", peoplesum: 129.42857142857142, date: "2018-06-10"}
{storeid: "", peoplesum: 147, date: "2018-06-11"}
{storeid: "", peoplesum: 123, date: "2018-06-12"}
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);
console.log("asdasd", dataNest);
// set the colour scale
var color = d3.scaleOrdinal(d3.schemeCategory10);
console.log("width", this.width);
console.log("width", dataNest.length);
var legendSpace = this.width / dataNest.length; // spacing for the legend
console.log("this.legendSpace", legendSpace);
// Loop through each symbol / key
dataNest.forEach(function (d, i) {
svgVar.append("path")
.attr("class", "line")
.style("stroke", function () { // Add the colours dynamically
return d.color = color(d.key);
})
.on("mouseover", onMouseOver)
.attr("d",d.peoplesum)
.attr("d", pricelineVar(d.values))
.attr("stroke-width", 3)
.style("fill", "none")
function onMouseOver(d, i) {
d3.select(this)
.attr('class', 'highlight');
d3.select(this)
.transition()
.duration(200)
//.attr('width', this.x.bandwidth() + 5)
.attr("y", function(d) { return this.y(d.peoplesum) - 10; })
.attr("height", function(d) { return height - this.y(d.peoplesum) + 10; })
.append("text")
.attr('class', 'val')
.attr('x', function() {
return this.X(d.date);
})
.attr('y', function() {
return this.Y(d.peoplesum) ;
})
}
// 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));
}
}
I'm not sure what pricelineVar(d.values) is doing here and The error i'm getting is :
**core.js:1598 ERROR TypeError: Cannot read property 'peoplesum' of undefined
at SVGPathElement.<anonymous> (d3graph.component.ts:140)**
By changing the code to the below snippet i was able to resolve the error
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", onMouseOver)
.attr("d", function(d) { return pricelineVar(d.values); })
.attr("fill","none");
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!
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
}
};
}
});