Skip to content

Instantly share code, notes, and snippets.

@JonathanDn
Created February 19, 2017 17:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JonathanDn/14a66c707fab89ecbf3626fc170358a5 to your computer and use it in GitHub Desktop.
Save JonathanDn/14a66c707fab89ecbf3626fc170358a5 to your computer and use it in GitHub Desktop.
Angular 2 - D3 Horizontal Scrollable Timeline, with mock date data / real log data - circles re-rendering when zoom activated & when side-scrolling - change colors according to input v1.0.0
import {Component, OnInit, Input, ChangeDetectionStrategy} from '@angular/core';
import * as d3 from 'd3';
import {Observable} from "rxjs";
import {UIConsts} from "../../../../shared/app.consts";
@Component({
selector: 'timeline',
styleUrls: ['timeline.scss'],
template: `
<div class="timeline-svg-container">
<div class="chart"></div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TimelineComponent implements OnInit {
private colorMap = {
itemType1: 'red',
itemType2: 'orange',
itemType3: '#00c2d6',
itemType4: 'green'
};
// Time Formats
private ts = Math.round(new Date().getTime() / 1000);
private tsYesterday = this.ts - (24 * 3600);
private tsMonthAgo = this.ts - (24 * 3000 * 30);
private tsWeekAgo = this.ts - (24 * 3000 * 7);
private dateNow: any = new Date(this.ts * 1000);
private dateTwentyFourHoursAgo: any = new Date(this.tsYesterday * 1000);
private dateMonthAgo: any = new Date(this.tsMonthAgo * 1000);
private dateWeekAgo: any = new Date(this.tsWeekAgo * 1000);
// Domain in Timestamps:
private tsDateNow = Date.parse(this.dateNow);
private tsDateTwentyFourHoursAgo = Date.parse(this.dateTwentyFourHoursAgo);
private tsDateMonthAgo = Date.parse(this.dateMonthAgo);
private tsDateWeekAgo = Date.parse(this.dateWeekAgo);
private margin = {top: 0, right: 20, bottom: 30, left: 20};
private width = 790 - this.margin.left - this.margin.right;
private height = 100 - this.margin.top - this.margin.bottom;
// Zoom Behavior
private zoom = d3.zoom()
// translateExtent - width - in charge of how many items can stack inside
.translateExtent([[0, +30], [this.width * 2, this.height]])
// scaleExtent - how much zoom scales.
.scaleExtent([1, 100])
.on ("zoom", () => {
// Zoom to a deeper Resolution:
this.zoomed();
// Re-Render
this.reRender();
});
private svg;
private viewBox;
private xAxis;
private xScale;
private gX;
@Input() logs;
constructor() { }
ngOnInit() {});
// Bar + scale + axis container
this.svg = d3.select('.chart')
.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('class', 'main-container')
.attr('transform', `translate(${this.margin.left}, ${this.margin.top})`);
// // Enable Zoom Behavior on SVG
this.svg.call(this.zoom);
// x Scale & Axis - domain in timestamps
this.xScale = d3
.scaleTime()
.domain([this.tsDateMonthAgo, this.tsDateNow])
.range([0, this.width]);
// // Bar above scale/axis
this.viewBox = this.svg.append('rect')
.attr('width', this.width)
.attr('height', this.height)
.style('fill', 'lightgrey')
.style('cursor', 'move');
this.xAxis = d3.axisBottom(this.xScale);
// Bottom x scale
this.gX = this.svg.append('g')
.attr('transform', `translate(0, ${this.height})`)
.attr('class', 'x axis')
.call(this.xAxis);
}
// MOCK DATA
private logsMoq = [{
CreationDateTime: "2017-02-12T07:02:44.033Z",
Type: "itemType1"
},
{
CreationDateTime: "2017-02-12T07:01:44.033Z",
Type: "itemType2"
},
{
CreationDateTime: "2017-02-12T07:00:44.033Z",
Type: "itemType3"
},
{
CreationDateTime: "2017-02-11T07:11:44.033Z",
Type: "itemType4"
},
{
CreationDateTime: "2017-02-11T07:10:44.033Z",
Type: "itemType1"
}];
// Update these on zoom:
render(data) {
let circles;
// JOIN
circles = this.svg.selectAll("circle")
.data(data);
// UPDATE - add circles
circles
.enter()
.append('g')
.attr('class', 'dot')
.append("circle")
.attr("r", 5)
.attr("cy", this.height)
.attr("cx", (d) => this.xScale(new Date(d.CreationDateTime).getTime()))
.style('fill', (d) => this.colorMap[d.Type])
.style('stroke', 'black')
.style('stroke-width', 2);
// EXIT
circles.exit().remove();
}
reRender() {
// Preserve logs data
let oldLogs = this.logs;
// Remove
this.logs = [];
// Update Original;
this.logs = oldLogs;
// Get logs back and Re-Render
this.render(oldLogs);
}
rePosition(transitionX, kFactor, data) {
let oldX = this.xScale(new Date(data.CreationDateTime).getTime());
let newXPosition = (transitionX + kFactor * oldX);
return newXPosition;
}
zoomed() {
// New Position:
let tX = d3.event.transform.x;
let k = d3.event.transform.k
// Zoom on bar
this.render(this.logs);
// Zoom / side-scroll on xAxis
this.gX.call(this.xAxis.scale(d3.event.transform.rescaleX(this.xScale)));
// Draw the CIRCLES in their new position:
d3.selectAll('circle').attr('cx', (d) => this.rePosition(tX, k, d));
}
ngAfterViewInit() {
this.render(this.logs);
}
}
.timeline-container {
position: relative;
width:792px;
height:102px;
.chart {
min-width: 790px;
min-height: 100px;
}
}
1. TODO - add types without exceptions: (and in other places like zoom);
// private svg: Selection<BaseType, {}, HTMLElement, any>;
// private viewBox: Selection<BaseType, {}, BaseType, {}>;
// private xAxis: Axis<any>;
// private xScale: ScaleTime<number, number>;
// private gX: Selection<BaseType, {}, BaseType, {}>;
2. TODO - make the initial zoom to be maximum zoom in --> instead of extent 0 initially -->
in a desired extent like 0-100 then initial view should be 50 if desired
3. TODO - time format --> should support both year / months / days / hours format, shorten strings of months from "February" --> "Feb"
without harming the zoom differnces in year / month / day....
//.tickFormat(d3.timeFormat("%I %M %p"));
4. TODO - PRIORITY(project specific) - RE RENDER THE DOTS(DELETE OLD PLACE NEW ONES OF NEW INCIDENT) --> when clicking other item in list.
5. TODO - PRIORITY(project specific) - sort logs - by date from left to right - "past -----> future".
// Then We have the array in a left to right order and appending of SORTED logs would be
// properly z-indexed/ z-transformed from left to right.
// let tsLogs = logs.map((d) => {
// d.CreationDateTime = Date.parse(d.CreationDateTime);
// return d });
// console.log('tsLogd', tsLogs);
// Sort Ascending
//let sortedLogs = tsLogs.sort((a, b) => a.CreationDateTime - b.CreationDateTime);
@bhumikasha
Copy link

Which version of D3 charts is this?

@JonathanDn
Copy link
Author

Which version of D3 charts is this?

I honestly don't remember. I do remember I build it pretty close to when Angular 2 first stable release was out of beta. So you could track that and and find out. Feel free to share here if you do I'll update it in the description for future reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment