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>
Related
So I have this code that works fine for the format date:
formatDate(date){
var aux= utcParse("%Y-%m-%dT%H:%M:%S.%LZ")(date);
return aux;
};
The chart looks like this:
However in the x-axis, the whole datetime object is rendered. I just wanted to show the day and the month in this format "day/month".
This is what I tried:
formatDate(date){
var aux= utcParse("%Y-%m-%dT%H:%M:%S.%LZ")(date);
var formated = timeFormat("%d-%m")(aux);
return formated;
};
However when I do this the data does not render. Before it even raised an error.
Please note that I am using a time axis for x:
// Add X axis --> it is a date format
var x = scaleTime()
.domain(extent(data_render, function(d) { return d[0]; }))
.range([ 0, width ]);
This is the whole source code of the React component.
import React, { Component } from 'react';
import { scaleLinear, scaleBand, scaleTime, scaleOrdinal } from 'd3-scale';
import { select, selectAll, pointer} from 'd3-selection';
import { line, curveMonotoneX, area } from 'd3-shape';
import { extent, max } from 'd3-array';
import { transition} from 'd3-transition';
import { axisBottom, axisLeft,axisRight } from "d3-axis";
import { timeParse, timeFormat , utcParse} from 'd3-time-format';
export default class MyLineChart extends Component {
constructor(props)
{
super(props);
this.state={"isLoaded":false, "circleHover":false};
this.lineRef = React.createRef();
this.formatDate=this.formatDate.bind(this);
this.circleTooltip=this.circleTooltip.bind(this);
}
circleTooltip(circle){
circle
.append("text")
.text("circle")
}
formatDate(date){
var aux= utcParse("%Y-%m-%dT%H:%M:%S.%LZ")(date);
//var formated = timeFormat("%d-%m")(aux);
//console.log(formated);
return aux;
//return timeFormat("%d-%m")(utcParse("%Y-%m-%dT%H:%M:%S.%LZ")(date));
};
componentDidMount(){
const node = this.lineRef.current;
const { data, title, aggr} = this.props;
var data_render = [];
data.segments.forEach(
(obj) => {
data_render.push([this.formatDate(obj.end), obj[title][aggr]]);
}
)
console.log(data_render);
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = select(node)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Add X axis --> it is a date format
var x = scaleTime()
.domain(extent(data_render, function(d) { return d[0]; }))
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(axisBottom(x));
// Add Y axis
var y = scaleLinear()
.domain([0, max(data_render, function(d) { return d[1]; })])
.range([ height, 0 ]).nice();
svg.append("g")
.attr("transform", "translate("+0+",0)")
.call(axisRight(y));
//Add the area
svg.append("path")
.datum(data_render)
.attr("fill", "#69b3a2")
.attr("fill-opacity", .3)
.attr("stroke", "none")
.attr("d", area()
.x(function(d) { return x(d[0]) })
.y0( height )
.y1(function(d) { return y(d[1]) }).curve(curveMonotoneX)
)
// Add the line
svg.append("path")
.datum(data_render)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", line()
.x(function(d) { return x(d[0]) })
.y(function(d) { return y(d[1]) }).curve(curveMonotoneX)
)
// Add the circles
svg.selectAll("myCircles")
.data(data_render)
.enter()
.append("circle")
.attr("fill", "red")
.attr("stroke", "none")
.attr("cx", function(d) { return x(d[0]) })
.attr("cy", function(d) { return y(d[1]) })
.attr("r", 5).style("opacity",1)
svg.selectAll("myText")
.data(data_render)
.enter()
.append("text")
.attr("x", function(d){return x(d[0])})
.attr("y", function(d){return y(d[1])})
.text(function(d){return d[0]+' '+d[1]})
.style("font-size","6px")
.style("opacity",1);
//Add the title
svg.append("text")
.attr("x", width/2)
.attr("y", margin.top)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.text(title);
this.setState({...this.state, isLoaded:true})
}
render() {
return (
<div>
<svg className="lineChart" ref={this.lineRef} />
</div>
);
}
}
How can I format the axis to just show the day and the month in the x-axis?
You want the values in data_render, like d[0], to be Date objects, which is what utcParse("%Y-%m-%dT%H:%M:%S.%LZ")(date) returns.
Then for your axis, you want something like d3.axisBottom(x).tickFormat(d3.timeFormat("%d-%m")).
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 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);
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 charting library to create charts with Angular-cli. D3 version is 4.2.2. Following is what I am trying.
app.component.ts
import { Component } from '#angular/core';
import * as D3 from 'd3';
import {ChartComponent} from './chart/chart.component'
#Component({
moduleId: module.id,
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.css'],
directives: [ChartComponent]
})
export class AppComponent {
title = 'app works!';
constructor() {
console.log(D3);
}
}
app.component.html
<app-chart></app-chart>
chart.component.ts
import {Component} from '#angular/core';
import {BarGraphDirective} from './bar-graph.directive';
#Component({
moduleId: module.id,
selector: 'app-chart',
templateUrl: 'chart.component.html',
styleUrls: ['chart.component.css'],
directives: [BarGraphDirective]
})
export class ChartComponent {
constructor() {}
}
chart.component.html
<bar-graph width="400" height="150" ></bar-graph>
Then here is code segment to add tooltip for data point of the chart.
BarGraphDirective
import {Directive, ElementRef, HostListener, Renderer} from '#angular/core';
import * as D3 from 'd3';
#Directive({
selector: 'bar-graph'
})
export class BarGraphDirective {
// private data:Array<number>; // raw chart data
private htmlElement:HTMLElement;
constructor(private elementRef:ElementRef, private renderer: Renderer) {
this.htmlElement = this.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
}, ...];
// 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");
var formatTime = d3.timeFormat("%e %B");
// set the ranges
var x = d3.scaleTime().range([0, width]);
.
var y = d3.scaleLinear().range([height, 0]);
var sales = function (d) {
return d["sales"];
}
var searches = function (d) {
return d.searches;
}
// 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("class", "bar-graph")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var color = d3.scaleOrdinal(d3.schemeCategory10);
var tooltip = d3.select("body").append("div")
.style("opacity", 0);
// 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")
.style("stroke-width", "2px");
// 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")
.style("stroke-width", "2px");
// Add sales to the scatterplot
svg.selectAll(".sales-circle")
.data(data)
.enter().append("circle")
.attr('class', 'sales-circle')
.attr("r", 4)
.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", 4)
.attr('class', 'searches-circle')
.attr("cx", function (d) {
return x(d.date);
})
.attr("cy", function (d) {
return y(d.searches);
})
.style("fill", "steelblue")
.on("mouseover", function (d) {
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(formatTime(d["date"]) + "<br/> Searches: " + d["searches"])
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 28) + "px")
.classed("tooltip", true);
})
.on("mouseout", function (d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
// draw legend
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function (d, i) {
return "translate(0," + i * 20 + ")";
});
// draw legend colored rectangles
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
// draw legend text
legend.append("text")
.style("font", "14px open-sans")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function (d) {
return d;
});
// Add the X Axis
svg.append("g")
.style("font", "14px open-sans")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickFormat(d3.timeFormat("%d/%m")));
// Add the Y Axis
svg.append("g")
.style("font", "14px open-sans")
.call(d3.axisLeft(y));
// Add Axis labels
svg.append("text")
.style("font", "14px open-sans")
.attr("text-anchor", "middle")
.attr("transform", "translate(" + (-margin.left / 2) + "," + (height / 2) + ")rotate(-90)")
.text("Sales / Searches");
svg.append("text")
.style("font", "14px open-sans")
.attr("text-anchor", "middle")
.attr("transform", "translate(" + (width / 2) + "," + (height + (margin.bottom)) + ")")
.text("Date");
}
#HostListener('mouseenter') onMouseEnter() {
this.highlight('yellow');
}
#HostListener('mouseleave') onMouseLeave() {
this.highlight(null);
}
private highlight(color: string) {
let d3:any = D3;
this.renderer.setElementStyle(d3.select('tooltip'), 'backgroundColor', color);
}
}
Then I updated chart.component.css as below.
app-chart bar-graph line .tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0;
border-radius: 8px;
pointer-events: none;
}
Now the problem is tooltip styles not applied.
Any suggestions are highly appreciated.
Thank You
Yes you can:
So instead of
tooltip.html(formatTime(d["date"]) + "<br/> Searches: " + d["searches"])
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 28) + "px")
.style("position", "absolute")
.style("width", "90px")
.style("height", "50px")
.style("padding", "2px")
.style("text-align", "center")
.style("font", "14px open-sans")
.style("background", "lightsteelblue")
.style("border", "0px")
.style("border-radius", "8px")
.style("pointer-events", "none");
You can do .classed("myStyle", true) you will need to define myStyle in the css:
tooltip.html(formatTime(d["date"]) + "<br/> Searches: " + d["searches"])
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 28) + "px")
.classed("myStyle", true);