Adding tooltips to candlestick using d3.js - javascript

I am using d3.js for a zoomable candlestick chart.It's working fine but I need to write a tool tip for showing OHLC values. I did some reference to try, but I failed. Can somebody help me to debug? Thank you very much!
Here is my code and it is very long, but for tooltips part, it's in the middle part.
function drawChart() {
d3.csv("amazon.csv").then(function(prices) {
const months = {0 : 'Jan', 1 : 'Feb', 2 : 'Mar', 3 : 'Apr', 4 : 'May', 5 : 'Jun', 6 : 'Jul', 7 : 'Aug', 8 : 'Sep', 9 : 'Oct', 10 : 'Nov', 11 : 'Dec'}
var dateFormat = d3.timeParse("%Y-%m-%d");
for (var i = 0; i < prices.length; i++) {
prices[i]['Date'] = dateFormat(prices[i]['Date'])
}
const margin = {top: 15, right: 65, bottom: 205, left: 50},
w = 1000 - margin.left - margin.right,
h = 625 - margin.top - margin.bottom;
var svg = d3.select("#container")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" +margin.left+ "," +margin.top+ ")");
let dates = _.map(prices, 'Date');
var xmin = d3.min(prices.map(r => r.Date.getTime()));
var xmax = d3.max(prices.map(r => r.Date.getTime()));
var xScale = d3.scaleLinear().domain([-1, dates.length])
.range([0, w])
var xDateScale = d3.scaleQuantize().domain([0, dates.length]).range(dates)
let xBand = d3.scaleBand().domain(d3.range(-1, dates.length)).range([0, w]).padding(0.3)
var xAxis = d3.axisBottom()
.scale(xScale)
.tickFormat(function(d) {
d = dates[d]
// hours = d.getHours()
// minutes = (d.getMinutes()<10?'0':'') + d.getMinutes()
// amPM = hours < 13 ? 'am' : 'pm'
// return hours + ':' + minutes + amPM + ' ' + d.getDate() + ' ' + months[d.getMonth()] + ' ' + d.getFullYear()
return d.getDate() + ' ' + months[d.getMonth()] + ' ' + d.getFullYear()
});
svg.append("rect")
.attr("id","rect")
.attr("width", w)
.attr("height", h)
.style("fill", "none")
.style("pointer-events", "all")
.attr("clip-path", "url(#clip)")
var gX = svg.append("g")
.attr("class", "axis x-axis") //Assign "axis" class
.attr("transform", "translate(0," + h + ")")
.call(xAxis)
gX.selectAll(".tick text")
.call(wrap, xBand.bandwidth())
var ymin = d3.min(prices.map(r => r.Low));
var ymax = d3.max(prices.map(r => r.High));
var yScale = d3.scaleLinear().domain([ymin, ymax]).range([h, 0]).nice();
var yAxis = d3.axisLeft()
.scale(yScale)
var gY = svg.append("g")
.attr("class", "axis y-axis")
.call(yAxis);
var chartBody = svg.append("g")
.attr("class", "chartBody")
.attr("clip-path", "url(#clip)");
// draw rectangles
let candles = chartBody.selectAll(".candle")
.data(prices)
.enter()
.append("rect")
.attr('x', (d, i) => xScale(i) - xBand.bandwidth())
.attr("class", "candle")
.attr('y', d => yScale(Math.max(d.Open, d.Close)))
.attr('width', xBand.bandwidth())
.attr('height', d => (d.Open === d.Close) ? 1 : yScale(Math.min(d.Open, d.Close))-yScale(Math.max(d.Open, d.Close)))
.attr("fill", d => (d.Open === d.Close) ? "silver" : (d.Open > d.Close) ? "red" : "green")
// draw high and low
let stems = chartBody.selectAll("g.line")
.data(prices)
.enter()
.append("line")
.attr("class", "stem")
.attr("x1", (d, i) => xScale(i) - xBand.bandwidth()/2)
.attr("x2", (d, i) => xScale(i) - xBand.bandwidth()/2)
.attr("y1", d => yScale(d.High))
.attr("y2", d => yScale(d.Low))
.attr("stroke", d => (d.Open === d.Close) ? "white" : (d.Open > d.Close) ? "red" : "green");
// -1- Create a tooltip div that is hidden by default:
let tooltip = d3.select("#container")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "black")
.style("border-radius", "5px")
.style("padding", "10px")
.style("color", "white");
// -2- Create 3 functions to show / update (when mouse move but stay on same circle) / hide the tooltip
let showTooltip = function(d) {
tooltip
.transition()
.duration(200);
tooltip
.style("opacity", 1)
.html("Open: " + d.Open)
.html("High: " + d.High)
.html("Low: " + d.Low)
.html("Close: " + d.Close)
.style("left", (d3.mouse(this)[0]+30) + "px")
.style("top", (d3.mouse(this)[1]+30) + "px")
};
let moveTooltip = function() {
tooltip
.style("left", (d3.mouse(this)[0]+30) + "px")
.style("top", (d3.mouse(this)[1]+30) + "px")
};
let hideTooltip = function() {
tooltip
.transition()
.duration(200)
.style("opacity", 0)
};
svg.append("defs")
.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", w)
.attr("height", h)
.on("mouseover", showTooltip )// -3- Trigger the functions
.on("mousemove", moveTooltip )
.on("mouseleave", hideTooltip);
const extent = [[0, 0], [w, h]];
var resizeTimer;
var zoom = d3.zoom()
.scaleExtent([1, 100])
.translateExtent(extent)
.extent(extent)
.on("zoom", zoomed)
.on('zoom.end', zoomend);
svg.call(zoom);
function zoomed() {
var t = d3.event.transform;
let xScaleZ = t.rescaleX(xScale);
let hideTicksWithoutLabel = function() {
d3.selectAll('.xAxis .tick text').each(function(d){
if(this.innerHTML === '') {
this.parentNode.style.display = 'none'
}
})
};
gX.call(
d3.axisBottom(xScaleZ).tickFormat((d, e, target) => {
if (d >= 0 && d <= dates.length-1) {
d = dates[d];
// hours = d.getHours()
// minutes = (d.getMinutes()<10?'0':'') + d.getMinutes()
// amPM = hours < 13 ? 'am' : 'pm'
// return hours + ':' + minutes + amPM + ' ' + d.getDate() + ' ' + months[d.getMonth()] + ' ' + d.getFullYear()
return d.getDate() + ' ' + months[d.getMonth()] + ' ' + d.getFullYear()
}
})
);
candles.attr("x", (d, i) => xScaleZ(i) - (xBand.bandwidth()*t.k)/2)
.attr("width", xBand.bandwidth()*t.k);
stems.attr("x1", (d, i) => xScaleZ(i) - xBand.bandwidth()/2 + xBand.bandwidth()*0.5);
stems.attr("x2", (d, i) => xScaleZ(i) - xBand.bandwidth()/2 + xBand.bandwidth()*0.5);
hideTicksWithoutLabel();
gX.selectAll(".tick text")
.call(wrap, xBand.bandwidth())
}
function zoomend() {
var t = d3.event.transform;
let xScaleZ = t.rescaleX(xScale);
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
var xmin = new Date(xDateScale(Math.floor(xScaleZ.domain()[0])));
xmax = new Date(xDateScale(Math.floor(xScaleZ.domain()[1])));
filtered = _.filter(prices, d => ((d.Date >= xmin) && (d.Date <= xmax)));
minP = +d3.min(filtered, d => d.Low);
maxP = +d3.max(filtered, d => d.High);
buffer = Math.floor((maxP - minP) * 0.1);
yScale.domain([minP - buffer, maxP + buffer]);
candles.transition()
.duration(800)
.attr("y", (d) => yScale(Math.max(d.Open, d.Close)))
.attr("height", d => (d.Open === d.Close) ? 1 : yScale(Math.min(d.Open, d.Close))-yScale(Math.max(d.Open, d.Close)));
stems.transition().duration(800)
.attr("y1", (d) => yScale(d.High))
.attr("y2", (d) => yScale(d.Low));
gY.transition().duration(800).call(d3.axisLeft().scale(yScale));
}, 500)
}
});
}
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
drawChart();

Related

Move g element for centering them in a tree layout

I would like move the g element include multiple tspan text to enter.
demo()
function demo() {
//subtitle("iptable filter")
// 2. 描画用のデータ準備
var width = 800
var height = 400
var data = {name:"AAA\nBBB",children: [
{name: "CCC\nDDD",children:[
{name:"EEE\nFFF"}
]},
{name: "GGG\nHHH",children:[
{name:"III\nJJJ"}
]},
{name: "KKK\nLLL",children: [
{name: "MMM\nNNN"}
]},
{name: "OOO\nPPP",children:[
{name: "QQQ\nRRR"}
]}
]}
var root = d3.hierarchy(data);
var tree = d3.tree()
.size([height, width])
tree(root);
var margin = {left:80,top:20,right:20,bottom:20}
var svg = d3.select('body').append("svg")
.attr('width',width + margin.left + margin.right)
.attr('height',height + margin.top + margin.bottom)
var g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`)
var link = g.selectAll(".link")
.data(root.descendants().slice(1))
.enter()
.append("path")
.attr("class", "link")
.attr("d", function(d) {
return "M" + d.y + "," + d.x +
"C" + (d.parent.y + 100) + "," + d.x +
" " + (d.parent.y + 100) + "," + d.parent.x +
" " + d.parent.y + "," + d.parent.x;
});
var node = g.selectAll(".node")
.data(root.descendants())
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
var txtnode = node.append("text")
.attr("text-anchor", 'start')
.attr("dominant-baseline","text-before-edge")//"middle"
.attr("font-size", "200%")
.selectAll('tspan')
.data(d => d.data.name.split('\n'))
.join('tspan')
.attr('class','tspan')
.attr('x',0)
.attr('y',(d,i) => i*25)
.text(d => d)
node.each((d,i,n) =>{
var bbox = d3.select(n[i]).node().getBBox()
var margin = 4
bbox.x -= margin
bbox.y -= margin
bbox.width += 2*margin
bbox.height += 2*margin
d.bbox = bbox
})
node.insert("rect",'text')
.attr('fill','#FEFECE')
.attr('fill','none')
.attr('rx',5.5)
.attr('ry',5.5)
.attr('stroke','#A80036')
.attr('stroke-width',2)
.attr('x',d => d.bbox.x)
.attr('y',d => d.bbox.y)
.attr('width',d => d.bbox.width)
.attr('height',d => d.bbox.height)
node.attr('dx',(d,i,n) => {
var x = d.bbox.width/2
return -x
})
.attr('dy',(d,i,n) => {
var x = d.bbox.width/2
var y = d.bbox.height/2
return -y
})
g.selectAll('.link')
.attr('fill','none')
.attr('stroke','#555')
.attr('stroke-opacity',1)
.attr('stroke-width',4)
}
<script src="https://d3js.org/d3.v6.min.js"></script>
The attribute dx and dy doesn't work in this example. what's the proper way to move the g element to make it move to center?
For repositioning them dynamically, an easy approach is getting the size of the element and translating it up/left by half its height/width:
node.each(function(d) {
const thisSize = this.getBoundingClientRect();
d3.select(this).attr("transform", `translate(${d.y - thisSize.width/2},${d.x - thisSize.height/2})`)
});
Here is your code with that change:
demo()
function demo() {
//subtitle("iptable filter")
// 2. 描画用のデータ準備
var width = 800
var height = 400
var data = {
name: "AAA\nBBB",
children: [{
name: "CCC\nDDD",
children: [{
name: "EEE\nFFF"
}]
},
{
name: "GGG\nHHH",
children: [{
name: "III\nJJJ"
}]
},
{
name: "KKK\nLLL",
children: [{
name: "MMM\nNNN"
}]
},
{
name: "OOO\nPPP",
children: [{
name: "QQQ\nRRR"
}]
}
]
}
var root = d3.hierarchy(data);
var tree = d3.tree()
.size([height, width])
tree(root);
var margin = {
left: 80,
top: 20,
right: 20,
bottom: 20
}
var svg = d3.select('body').append("svg")
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
var g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`)
var link = g.selectAll(".link")
.data(root.descendants().slice(1))
.enter()
.append("path")
.attr("class", "link")
.attr("d", function(d) {
return "M" + d.y + "," + d.x +
"C" + (d.parent.y + 100) + "," + d.x +
" " + (d.parent.y + 100) + "," + d.parent.x +
" " + d.parent.y + "," + d.parent.x;
});
var node = g.selectAll(".node")
.data(root.descendants())
.enter()
.append("g")
.attr("class", "node");
var txtnode = node.append("text")
.attr("text-anchor", 'start')
.attr("dominant-baseline", "text-before-edge") //"middle"
.attr("font-size", "200%")
.selectAll('tspan')
.data(d => d.data.name.split('\n'))
.join('tspan')
.attr('class', 'tspan')
.attr('x', 0)
.attr('y', (d, i) => i * 25)
.text(d => d)
node.each((d, i, n) => {
var bbox = d3.select(n[i]).node().getBBox()
var margin = 4
bbox.x -= margin
bbox.y -= margin
bbox.width += 2 * margin
bbox.height += 2 * margin
d.bbox = bbox
})
node.insert("rect", 'text')
.attr('fill', '#FEFECE')
.attr('fill', 'none')
.attr('rx', 5.5)
.attr('ry', 5.5)
.attr('stroke', '#A80036')
.attr('stroke-width', 2)
.attr('x', d => d.bbox.x)
.attr('y', d => d.bbox.y)
.attr('width', d => d.bbox.width)
.attr('height', d => d.bbox.height)
node.attr('dx', (d, i, n) => {
var x = d.bbox.width / 2
return -x
})
.attr('dy', (d, i, n) => {
var x = d.bbox.width / 2
var y = d.bbox.height / 2
return -y
})
g.selectAll('.link')
.attr('fill', 'none')
.attr('stroke', '#555')
.attr('stroke-opacity', 1)
.attr('stroke-width', 4)
node.each(function(d) {
const thisSize = this.getBoundingClientRect();
d3.select(this).attr("transform", `translate(${d.y - thisSize.width/2},${d.x - thisSize.height/2})`)
});
}
<script src="https://d3js.org/d3.v6.min.js"></script>

JQuery SVG graph - overlapping bars

I'm trying to learn SVG graphs, I've nearly completed my graph however I currently have overlapping bars, I believe this is due to a combination of my x axis return data and my json data. My x axis lists the months from my JSON data, but my JSON data can have multiple entries from the same month. and if this is the case then my entries will overlap for that month. What I need to do is return the month + day seperately, however I haven't been able to figure out how just yet.
JSON data example where there's 2 entries for the same month:
1: {dayNo: 2, monthNo: 7, monthName: "July", year: 2018, recordDate: "2018-07-02T00:00:00",…}
avgVolume: 3585.53
dayNo: 2
monthName: "July"
monthNo: 7
recordDate: "2018-07-02T00:00:00"
volumeLastYear: 4109.47
volumeToday: 3575.34
year: 2018
2: {dayNo: 30, monthNo: 7, monthName: "July", year: 2018, recordDate: "2018-07-30T00:00:00",…}
avgVolume: 3291.55
dayNo: 30
monthName: "July"
monthNo: 7
recordDate: "2018-07-30T00:00:00"
volumeLastYear: 3996.31
volumeToday: 3414.44
year: 2018
And here is the svg code:
$request.done(function (response) {
// remove old svg
$('svg').remove();
// create svg element
var $svg = d3.select('.card.mb-3.clickable-card.highlight')
.append('svg')
.attr('width', '100%')
.attr('height', '400px')
.attr('style', 'background-color: lavender')
.classed('svg display-svg', true);
// 2 determine size of svg element
var w = $svg.node().getBoundingClientRect().width;
var h = $svg.node().getBoundingClientRect().height;
// 10 chart margins
var chartMargin = new Object();
chartMargin.left = 40;
chartMargin.right = 25;
chartMargin.top = 25;
chartMargin.bottom = 40;
// 10 cont
h = h - chartMargin.bottom - chartMargin.top;
// Max volume
var maxVolume = d3.max(response, d => d.avgVolume);
// bar Margins / width
var barMargin = 10;
var barWidth = (w - chartMargin.left - chartMargin.right) / response.length;
// yScale
var yScale = d3.scaleLinear()
.domain([0, maxVolume])
.range([h, chartMargin.top]);
// xScale
var xScale = d3.scaleBand()
.domain(response.map(function (d) { return d.monthName; }))
.range([0, w - chartMargin.left - chartMargin.right])
.paddingInner(0.15);
// chartGroup
var chartGroup = $svg.append('g')
.classed('chartGroup', true)
.attr('transform', 'translate(' + chartMargin.left + ',' + chartMargin.top + ')');
// 5
var barGroups = chartGroup
.selectAll('g')
.data(response);
// 6
var newBarGroups = barGroups.enter()
.append('g')
.attr('transform', function (d, i) {
return 'translate('
+ (xScale(d.monthName))
+ ','
+ (yScale(d.avgVolume))
+ ')';
})
// yAxis label
var yAxis = d3.axisLeft(yScale);
chartGroup.append('g')
.classed('axis y', true)
.attr('transform', 'translate(' + -20 + ', ' + h / 2 + ')')
.append('text')
.attr('transform', 'rotate(-90)')
.attr('dx', '-0.8em')
.attr('dy', '0.25em')
.attr("text-anchor", 'middle')
.text("Reservoir Volume (ML)");
// xAxis label
var xAxis = d3.axisBottom(xScale);
chartGroup.append('g')
.attr('transform', 'translate(0,' + h + ')')
.classed('axis x', true)
.call(xAxis);
newBarGroups
.append('rect')
.attr('x', 0)
.attr('height', function (d, i) {
return h - yScale(d.avgVolume);
})
// animation
.style('fill', 'transparent')
.transition().duration(function (d, i) { return i * 500; })
.delay(function (d, i) { return i + 200; })
.attr('width', barWidth - barMargin)
.style('fill', function (d, index) {
return "hsl(240, 100%, " + (d.avgVolume / maxVolume * 80) + "%)"
});
// bar text
newBarGroups
.append('text')
.attr('transform', 'rotate(-45)')
.attr('text-anchor', 'middle')
.attr('x', function () { return 0; })
.attr('y', 50)
.attr('fill', 'white')
.text(function (d, i) { return d.avgVolume; })
.style('font-size', '1em')
});
in the xScale, I have tried changing the return data from
return d.monthName
to
return d.monthName + ' ' + d.dayNo
But then all my bars overlap and I get this error for each g element:
d3.js:1211 Error: attribute transform: Expected number, "translate(undefined,25.183…".
Here is what I have currently with returning d.monthName:
Here is d.monthName + ' ' + d.dayNo:
Nevermind sorry, I fixed it
I needed to change
var newBarGroups = barGroups.enter()
.append('g')
.attr('transform', function (d, i) {
return 'translate('
+ (xScale(d.monthName))
+ ','
+ (yScale(d.avgVolume))
+ ')';
})
to
var newBarGroups = barGroups.enter()
.append('g')
.attr('transform', function (d, i) {
return 'translate('
+ (xScale(d.monthName + ' ' + d.dayNo))
+ ','
+ (yScale(d.avgVolume))
+ ')';
})

D3.js y- axis ticks and grid lines do not align

In my d3 bar chart, I should have Y-axis dynamic ticks and grid lines for integer values (no ticks and grid lines for decimal values). But its not working , Please help me on this
var itemData =[{month: "MARCH", year: 2018, number: 26, date:1519842600000},{month: "MAY", year: 2018, number: 34, date: 1525113000000}];
createChart(itemData )
createChart(itemData) {
const router_temp = this.router;
const element = this.chartContainer.nativeElement;
const factoryId = itemData['id'];
const data = itemData.data;
d3.select(element).selectAll('*').remove();
const div = d3.select('body').append('div')
.attr('class', 'tooltip-bar-chart')
.style('display', 'none');
const svg = d3.select(element),
margin = {top: 10, right: 10, bottom: 20, left: 30},
width = +this.el.nativeElement.offsetWidth - margin.left - margin.right,
height = +svg.attr('height') - margin.top - margin.bottom;
const x = d3.scaleBand().range([10, width - 10]).padding(1),
y = d3.scaleLinear().rangeRound([height, 0]);
// add the X gridlines
svg.append('g')
.attr('class', 'grid')
.attr('transform', 'translate(30,' + height + ')')
.call(make_x_gridlines()
.tickSize(-height)
.tickFormat('')
);
// add the Y gridlines
svg.append('g')
.attr('class', 'grid')
.attr('transform', 'translate(30, 10)')
.call(make_y_gridlines()
.tickSize(-width)
.tickFormat('')
);
const g = svg.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
x.domain(data.map(function(d) { return d.month; }));
y.domain([0, d3.max(data, function(d) { return d.number; })]);
//
g.selectAll('.bar')
.data(data)
.enter().append('rect')
.attr('class', 'bar')
.attr('x', function(d) { return x(d.month); })
.attr('y', function(d) { return y(d.number); })
.attr('width', 12)
.attr('height', function(d) { return height - y(d.number); })
.on('mouseover', function(d) {
div.style('display', 'block');
div.html(
'<div class=\'main\'>' +
'<div class=\'month\'>' +
d.month +
'</div>' +
'<div class=\'number\'>' +
d.number +
'</div>' +
'<div class=\'bottom\'>' +
'Number of Applications Transformed & Migrated' +
'</div>' +
'</div>'
)
.style('left', (d3.event.pageX + 15) + 'px')
.style('top', (d3.event.pageY - 50) + 'px');
})
.on('mouseout', function(d) {
div.style('display', 'none');
})
const dataLength = data.length;
const xPositions = [];
for (let i = 0; i < dataLength; i += 1) {
xPositions.push(x(data[i].month) + margin.left);
}
const newX = d3.scaleOrdinal().range(xPositions);
const xScale = newX.domain(itemData.xlabels);
svg.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + (height + 10) + ')')
.call(d3.axisBottom(xScale));
g.append('g')
.attr('class', 'axis axis--y')
.call(d3.axisLeft(y).ticks(5));
// gridlines in x axis function
function make_x_gridlines() {
return d3.axisBottom(x)
.ticks(1);
}
// gridlines in y axis function
function make_y_gridlines() {
return d3.axisLeft(y)
.ticks(5);
}
}
}
Here is sample
I created a fiddle.
that while I had to change a few things to account for no angular, but shows the axis gridlines and labels aligned. I moved everything into one g group is is then translated and the individual components are no longer translated.

Legend not generating

I'm trying to build a legend to a calendar view graph using d3 js like this one
but i'm trying to build a legend to it like the kind of shown in the pic
but i'm not able to generate this
here's my code to generated calender view map
var Comparison_Type_Max = d3.max(inputData, function(d) { return d.value; });
var data = d3.nest()
.key(function(d) { return d.Date; })
.rollup(function(d) { return (d[0].value ); })
.map(inputData);
var margin = { top: 20, right: 50, bottom: 10, left: 60 },
width = $(dom_element_to_append_to).parent().width() - margin.left - margin.right,
cellSize = Math.floor(width/52),
height = cellSize * 7 + margin.top + margin.bottom ,
week_days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
month = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
$rootScope.defaultWidth = width;
$rootScope.cellSize = cellSize;
var day = d3.time.format("%w"),
week = d3.time.format("%U"),
percent = d3.format(".1%"),
format = d3.time.format("%Y%m%d"),
legendElementWidth = cellSize * 2,
buckets = 9,
parseDate = d3.time.format("%Y%m%d").parse;
var color = d3.scale.linear().range(colorRange)
.domain([0, 1]);
var colorScale = d3.scale.quantile()
.domain([0, buckets - 1, d3.max(data, function (d) { return d.value; })])
.range(colorRange);
var svg = d3.select(dom_element_to_append_to).selectAll("svg")
.data(d3.range(year, year+1))
.enter().append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("data-height", '0.5678')
.attr("class", "RdYlGn")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//.attr("transform", "translate(" + ((width - cellSize * 53) / 2) + "," + (height - cellSize * 7 - 1) + ")");
console.log("1");
svg.append("text")
.attr("transform", "translate(-38," + cellSize * 3.5 + ")rotate(-90)")
.style("text-anchor", "middle")
.text(function(d) { return d; });
for (var i=0; i<7; i++)
{
svg.append("text")
.attr("transform", "translate(-5," + cellSize*(i+1) + ")")
.style("text-anchor", "end")
.attr("dy", "-.25em")
.text(function(d) { return week_days[i]; });
}
var rect = svg.selectAll(".day")
.data(function(d) { return d3.time.days(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
.enter()
.append("rect")
.attr("class", "day")
.attr("width", cellSize)
.attr("height", cellSize)
.attr("x", function(d) { return week(d) * cellSize; })
.attr("y", function(d) { return day(d) * cellSize; })
.attr("fill",'#fff')
.datum(format);
console.log("2");
/* var legend = svg.selectAll(".legend")
.data(data)
.enter().append("g")
.attr("class", "legend");
//.attr("transform", function(d, i) { return "translate(" + (((i+1) * cellSize*4.33)) + ",0)"; });
legend.append("rect")
.attr("x", function(d, i) { return legendElementWidth * i; })
.attr("y", height)
.attr("width", legendElementWidth)
.attr("height", cellSize / 2)
.style("fill", function(d, i) { return color(Math.sqrt(data[d] / Comparison_Type_Max )); });
legend.append("text")
.attr("class", "mono")
.text(function(d) { return "≥ " + Math.round(d); })
.attr("x", function(d, i) { return legendElementWidth * i; })
.attr("y", height + cellSize);
*/
var legend = svg.selectAll(".legend")
.data([0].concat(colorScale.quantiles()), function(d) { return d; });
legend.enter().append("g")
.attr("class", "legend");
legend.append("rect")
.attr("x", function(d, i) { return legendElementWidth * i; })
.attr("y", height)
.attr("width", legendElementWidth)
.attr("height", cellSize / 2)
.style("fill", function(d, i) { return color(Math.sqrt(data[i] / Comparison_Type_Max )); });
legend.append("text")
.attr("class", "mono")
.text(function(d) { return "≥ " + Math.round(d); })
.attr("x", function(d, i) { return legendElementWidth * i; })
.attr("y", height + cellSize);
legend.exit().remove();
svg.selectAll(".month")
.data(function(d) { return d3.time.months(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
.enter().append("path")
.attr("class", "month")
.attr("id", function(d,i){ return month[i] + "" + varID })
.attr("d", monthPath);
console.log("4");
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
var year = d/10000;
year = Math.floor(year);
var month = d/100;
month = Math.floor(month % 100);
var day = d % 100;
if(day<10) day = "0" + day;
if(month < 10) month = "0" + month;
if(isCurrency)
return "<div><span>Date:</span> <span style='color:white'>" + day + "/" + month + "/" + year + "</span></div>" +
"<div><span>Value:</span> <span style='color:white'><span>&#8377</span>" + d3.format(",")(data[d]) + "</span></div>";
else
return "<div><span>Date:</span> <span style='color:white'>" + day + "/" + month + "/" + year + "</span></div>" +
"<div><span>Value:</span> <span style='color:white'>" + d3.format(",")(data[d]) + "</span></div>";
});
console.log("5");
svg.call(tip);
console.log("6");
rect.filter(function(d) { return d in data; })
.on("click", function(d){
console.log(d);
var year = d/10000;
year = Math.floor(year);
var monthInt = d/100;
console.log(dom_element_to_append_to);
var val,id;
if(dom_element_to_append_to=='.sms-yearheatmap-container-negative')
val = 0;
else if (dom_element_to_append_to=='.sms-yearheatmap-container-positive')
val = 1;
else val = 2;
monthInt = Math.floor(monthInt % 100);
for (var itr = 0; itr<12; ++itr) {
id = month[itr] + "" + varID;
$('#' + id).css("z-index",0);
$('#' + id).css("stroke","#000");
$('#' + id).css("stroke-width", "2.5px");
}
id = month[monthInt-1] + "" + varID;
$('#' + id).css("stroke","blue");
$('#' + id).css("position","relative");
$('#' + id).css("z-index",1000);
$('#' + id).css("stroke-width", "4.5px");
console.log("year " + year + " month" + monthInt);
$rootScope.loadDayHourHeatGraph(year, monthInt , val, isCurrency);
})
.attr("fill", function(d) { return color(Math.sqrt(data[d] / Comparison_Type_Max )); })
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
/*.attr("data-title", function(d) { return "value : "+Math.round(data[d])});
$("rect").tooltip({container: 'body', html: true, placement:'top'}); */
/* $("rect").on("click", function(d) {
console.log(d);
});*/
function numberWithCommas(x) {
x = x.toString();
var pattern = /(-?\d+)(\d{3})/;
while (pattern.test(x))
x = x.replace(pattern, "$1,$2");
return x;
it is generting the graph correctly but my legend is not generating with it
here's the code of legend which i wrote above
var legend = svg.selectAll(".legend")
.data([0].concat(colorScale.quantiles()), function(d) { return d; });
legend.enter().append("g")
.attr("class", "legend");
legend.append("rect")
.attr("x", function(d, i) { return legendElementWidth * i; })
.attr("y", height)
.attr("width", legendElementWidth)
.attr("height", cellSize / 2)
.style("fill", function(d, i) { return color(Math.sqrt(data[i] / Comparison_Type_Max )); });
legend.append("text")
.attr("class", "mono")
.text(function(d) { return "≥ " + Math.round(d); })
.attr("x", function(d, i) { return legendElementWidth * i; })
.attr("y", height + cellSize);
legend.exit().remove();
i don't want to use the colorscale variable instead i want to generate the legend according to the color variable defined in the main code, the colorscale variable is not used anywhere in the code but only in the legend just for a start, the whole calender view graph is generated using the the colorscheme provided by the color variable

d3.js calendar view weeks in rows

Can anybody help me convert the d3.js Calendar View implementation which looks like the one depicted here (weeks as columns): D3 Calendar View using Associative Array
to something more like this (weeks as rows): http://kamisama.github.io/cal-heatmap/v2/ (See Months > days (horizontally) section)
I've had a go at playing around with the axes, but to no avail.
Any help would be greatly appreciated.
My current code looks like this:
raApp.directive('heatMapYear', function () {
var width = 1200,
height = 150,
cellSize = 17; // cell size
var day = d3.time.format("%w"),
week = d3.time.format("%U"),
month = d3.time.format("%m"),
monthName = d3.time.format("%b"),
format = d3.time.format("%Y-%m-%d");
var color = d3.scale.quantize()
.domain([1, 5])
.range(d3.range(5).map(function(d) { return "rank" + d; }));
return {
restrict: 'A'
, replace: false
, scope: {
chartData: '='
, dateField: '='
}
, link: function (scope, element, attrs) {
scope.$watch('chartData', function(newData, oldData) {
d3.select(element[0]).selectAll('*').remove();
if (newData != undefined) {
var svg = d3.select(element[0]).selectAll("svg")
.data(function() {
var years = [];
for (var i = 0; i < newData.length; i++) {
var date = newData[i][scope.dateField];
var year = parseInt(date.substring(0, 4));
if (years.indexOf(year) == -1) {
years.push(year);
}
}
return years;
})
.enter().append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "heatClass")
.append("g")
.attr("transform", "translate(" + 50 + "," + (height - cellSize * 7 - 1) + ")");
svg.append("text")
.attr("transform", "translate(-30," + cellSize * 3.5 + ")rotate(-90)")
.style("text-anchor", "middle")
.style("font-weight", "bold")
.text(function(d) { return d; });
svg.selectAll(".monthName")
.data(function(d) { return d3.time.months(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
.enter().append("text")
.attr("x", function(d) {return (week(d) * cellSize + 50); })
.attr("y", -5)
.style("text-anchor", "middle")
.text(function(d) { return monthName(d); });
svg.selectAll(".dayName")
.data(function(d) { return ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"] })
.enter().append("text")
.attr("x", -10)
.attr("y", function(d, i) {return (i * cellSize) + 12; })
.style("text-anchor", "middle")
.text(function(d) { return d; });
var svg1 = d3.select(element[0]).select("svg")
var legend = svg1.selectAll(".legend")
.data([0])
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(130," + (i * (cellSize) + 30) + ")";
});
legend.append("svg:image")
.attr("xlink:href", "img/RA-scale-small.png")
.attr("x", width - 350)
.attr("y", 0)
.attr("width",200)
.attr("height", 47);
var rect = svg.selectAll(".day")
.data(function(d) { return d3.time.days(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
.enter().append("rect")
.attr("class", "day")
.attr("width", cellSize)
.attr("height", cellSize)
.attr("x", function(d) { return week(d) * cellSize; })
.attr("y", function(d) { return day(d) * cellSize; })
.datum(format);
rect.append("title")
.text(function(d) { return d; });
svg.selectAll(".month")
.data(function(d) { return d3.time.months(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
.enter().append("path")
.attr("class", "month")
.attr("d", monthPath);
var data = d3.nest()
.key(function(d) { return d[scope.dateField]; })
.rollup(function(d) {return {rank:d[0]["rank"],revenue:d[0]["abbrRevenue"],volume:d[0]["abbrVolume"]}})
.map(newData);
rect.filter(function(d) { return d in data; })
.attr("class", function(d) {return "day " + color(data[d].rank); })
.select("title")
.text(function(d) { return d + "\nRevenue: " + data[d].revenue + "\nVolume: " + data[d].volume });
}
});
function monthPath(t0) {
var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0),
d0 = +day(t0), w0 = +week(t0),
d1 = +day(t1), w1 = +week(t1);
return "M" + (w0 + 1) * cellSize + "," + d0 * cellSize
+ "H" + w0 * cellSize + "V" + 7 * cellSize
+ "H" + w1 * cellSize + "V" + (d1 + 1) * cellSize
+ "H" + (w1 + 1) * cellSize + "V" + 0
+ "H" + (w0 + 1) * cellSize + "Z";
}
}
}
});
After much digging around and trial and error, I figured out a solution to this. Therefore I wanted to share the code in case it helps anyone else out there looking to achieve similar results.
The code is as follows:
raApp.directive('heatMapYearWeekRows', function () {
var width = 1490,
height = 200,
cellSize = 17; // cell size
var day = d3.time.format("%w"),
week = d3.time.format("%U"),
month = d3.time.format("%m"),
monthName = d3.time.format("%b"),
format = d3.time.format("%Y-%m-%d"),
displayFormat = d3.time.format("%a, %d %b %Y");
var color = d3.scale.quantize()
.domain([1, 5])
.range(d3.range(5).map(function(d) { return "rank" + d; }));
return {
restrict: 'A'
, replace: false
, scope: {
chartData: '='
, dateField: '='
}
, link: function (scope, element, attrs) {
scope.$watch('chartData', function(newData, oldData) {
d3.select(element[0]).selectAll('*').remove();
if (newData != undefined) {
var svg = d3.select(element[0]).selectAll("svg")
.data(function() {
var years = [];
for (var i = 0; i < newData.length; i++) {
var date = newData[i][scope.dateField];
var year = parseInt(date.substring(0, 4));
if (years.indexOf(year) == -1) {
years.push(year);
}
}
return years;
})
.enter().append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "heatClass")
.append("g")
.attr("transform", "translate(" + 50 + "," + (height - cellSize * 7 - 1) + ")");
svg.append("text")
.attr("transform", "translate(-30," + cellSize * 3.5 + ")rotate(-90)")
.style("text-anchor", "middle")
.style("font-weight", "bold")
.text(function(d) { return d; });
svg.selectAll(".monthName")
.data(function(d) { return d3.time.months(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
.enter().append("text")
.attr("x", function(d) {
return ((month(d) - 1) * (7 * cellSize) + 50);
})
.attr("y", 115)
.style("text-anchor", "middle")
.style("font-weight", "bold")
.text(function(d) { return monthName(d); });
var svg1 = d3.select(element[0]).select("svg")
var legend = svg1.selectAll(".legend")
.data([0])
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(130," + (i * (cellSize) + 30) + ")";
});
legend.append("svg:image")
.attr("xlink:href", "img/RA-scale-small.png")
.attr("x", -80)
.attr("y", -30)
.attr("width",200)
.attr("height", 47);
var rect = svg.selectAll(".day")
.data(function(d) { return d3.time.days(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
.enter().append("rect")
.attr("class", "day")
.attr("width", cellSize)
.attr("height", cellSize)
.attr("x", function(d) {
var prevDay = new Date(d -1);
var monthOffset = (month(d) - 1) * (7 * cellSize);
var result = (day(d) * cellSize) + +monthOffset;
return result; })
.attr("y", function(d) {
var result = ((getMonthWeek(d) - 1) * cellSize);
return result; })
.datum(format);
rect.append("title")
.text(function(d) {
return d;
});
svg.selectAll(".month")
.data(function(d) { return d3.time.months(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
.enter().append("path")
.attr("class", "month")
.attr("d", monthPath);
var data = d3.nest()
.key(function(d) { return d[scope.dateField]; })
.rollup(function(d) {return {rank:d[0]["rank"],revenue:d[0]["abbrRevenue"],volume:d[0]["abbrVolume"]}})
.map(newData);
rect.filter(function(d) { return d in data; })
.attr("class", function(d) {return "day " + color(data[d].rank); })
.select("title")
.text(function(d) { return displayFormat(new Date(d)) + "\nRevenue: " + data[d].revenue + "\nVolume: " + data[d].volume });
}
});
function getMonthWeek(date){
var firstDay = new Date(date.getFullYear(), date.getMonth(), 1).getDay();
return Math.ceil((date.getDate() + firstDay)/7);
}
function monthPath(t0) {
var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0),
d0 = +day(t0), w0 = +getMonthWeek(t0) -1, m0 = +month(t0),
d1 = +day(t1), w1 = +getMonthWeek(t1) -1;
var monthOffsetX = (+m0 - 1) * (7 * cellSize);
return "M" + ((d0 * cellSize) + +monthOffsetX) + "," + ((w0 + 1) * cellSize)
+ "V" + w0 * cellSize + "H" + ((7 * cellSize) + +monthOffsetX)
+ "V" + w1 * cellSize + "H" + (((d1 + 1) * cellSize) + +monthOffsetX)
+ "V" + ((w1 + 1) * cellSize) + "H" + +monthOffsetX
+ "V" + ((w0 + 1) * cellSize) + "Z";
}
}
}
});
Hope that helps.

Categories