d3js bars not updating properly - d3.js

I created this bars with a tooltip. I need to get them update after the $('.quarter-increase, .quarter-decrease').on('click', function() {
I don't get any errors but nothing gets updated...
$(document).ready(function() {
$('#prof-rendi').click(function() {
$('.graph-loading').show();
$('#svg-quarter').empty();
var tooltip = tooltipd3();
var svg = d3.select("svg#svg-quarter"),
margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
y = d3.scaleLinear().rangeRound([height, 0]);
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var div = d3.select("#svg-quarter").append("div"). // declare the tooltip div
attr("class", "tooltip"). // apply the 'tooltip' class
style("opacity", 0);
d3.csv(base_url() + 'graph/getStatementsQuarterly/', function(d) {
$('.graph-loading').hide();
d.guadagno = +d.guadagno;
return d;
}, function(error, data) {
if (error)
throw error;
x.domain(data.map(function(d) {
return d.periodo;
}));
y.domain([
0,
d3.max(data, function(d) {
return d.guadagno;
})
]);
g.append("g").attr("class", "axis axis--x").attr("transform", "translate(0," + height + ")").call(d3.axisBottom(x));
g.append("g").attr("class", "axis axis--y").call(d3.axisLeft(y).ticks(10)).append("text").attr("transform", "rotate(-90)").attr("y", 6).attr("dy", "0.71em").attr("text-anchor", "end").text("Guadagno")
g.selectAll(".bar").data(data).enter().append("rect").attr("class", "bar").attr("x", function(d) {
return x(d.periodo);
}).attr("y", function(d) {
return y(d.guadagno);
}).attr("width", x.bandwidth()).attr("height", function(d) {
return height - y(d.guadagno);
}).on('mouseover', function(d) {
var html = '<h5>' + d.guadagno + ' €</h5>';
tooltip.mouseover(html); // pass html content
}).on('mousemove', tooltip.mousemove).on('mouseout', tooltip.mouseout);
});
});
$('.quarter-increase, .quarter-decrease').on('click', function() {
$('.rendi-btn.left, .rendi-btn.right').attr('disabled', 'disabled');
var where_at = $('#scroll-statement-quarter').val();
$('.graph-loading').show();
$('#svg-quarter').css({'opacity': 0.4});
var tooltip = tooltipd3();
var svg = d3.select("svg#svg-quarter"),
margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
y = d3.scaleLinear().rangeRound([height, 0]);
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var div = d3.select("#svg-quarter").append("div"). // declare the tooltip div
attr("class", "tooltip"). // apply the 'tooltip' class
style("opacity", 0);
var speed = 500;
d3.csv(base_url() + 'graph/getStatementsQuarterly/' + where_at, function(d) {
$('.graph-loading').hide();
d.guadagno = +d.guadagno;
return d;
}, function(error, data) {
if (error)
throw error;
x.domain(data.map(function(d) {
return d.periodo;
}));
y.domain([
0,
d3.max(data, function(d) {
return d.guadagno;
})
]);
g.append("g").attr("class", "axis axis--x").attr("transform", "translate(0," + height + ")").call(d3.axisBottom(x));
g.append("g").attr("class", "axis axis--y").call(d3.axisLeft(y).ticks(10)).append("text").attr("transform", "rotate(-90)").attr("y", 6).attr("dy", "0.71em").attr("text-anchor", "end").text("Guadagno")
g.selectAll(".bar").data(data).transition().duration(speed).attr("class", "bar").attr("x", function(d) {
return x(d.periodo);
}).attr("y", function(d) {
return y(d.guadagno);
}).attr("width", x.bandwidth()).attr("height", function(d) {
return height - y(d.guadagno);
}).on('mouseover', function(d) {
var html = '<h5>' + d.guadagno + ' €</h5>';
tooltip.mouseover(html); // pass html content
}).on('mousemove', tooltip.mousemove).on('mouseout', tooltip.mouseout);
});
})
});
This is a Plunker to test this:
https://plnkr.co/edit/72GCWqkllMFXZI6mecQE?p=preview
Press "show", then change the year to 2016 and you will see the result.

Your g variable inside the click event handler is a newly appended <group> element.
Therefore, this...
g.selectAll(".bar").data(data).etc...
... won't work, because there is nothing with a class .bar inside that group.
Solution: use the svg variable to select the rectangles:
svg.selectAll(".bar").data(data).etc...
Here is the updated plunker: https://plnkr.co/edit/eNa6Af0WcyrcLejadO2q?p=preview
PS: this code has several problems. I strongly advise you to not mix jQuery and D3, and also to not use d3.csv inside an event handler.

Related

D3 Typeerror cannot read property of undefined

I'm trying to follow this guide, but implementing my own data:
https://www.d3-graph-gallery.com/graph/stackedarea_basic.html
Here is my function
getStackedAreaChart: function(pod) {
//eval is sligtly heavily used here.
var cssName = ".stackedareachart-" + pod;
var podData = eval("this.StackedAreaChartData" + pod);
var ListName = eval("this.List" + pod);
// 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 = d3
.select(cssName)
.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 + ")");
var sumstat = d3
.nest()
.key(function(d) {
return d.time;
})
.entries(podData);
console.log(sumstat);
// Stack the data: each group will be represented on top of each other
var mygroups = ListName; // list of group names
var mygroup = []; // list of group names
for (let i = 1; i <= mygroups.length; i++) {
mygroup.push(i);
}
console.log(mygroups);
console.log(mygroup);
var stackedData = d3
.stack()
.keys(mygroup)
.value(function(d, key) {
return d.values[key].interactionCount;
})(sumstat);
// Add X axis --> it is a date format
var x = d3
.scaleLinear()
.domain(
d3.extent(podData, function(d) {
return d.time;
})
)
.range([0, width]);
svg
.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(5));
// Add Y axis
var y = d3
.scaleLinear()
.domain([
0,
d3.max(podData, function(d) {
return +d.interactionCount;
})
])
.range([height, 0]);
svg.append("g").call(d3.axisLeft(y));
// color palette
var color = d3
.scaleOrdinal()
.domain(mygroups)
.range([
"#e41a1c",
"#377eb8",
"#4daf4a",
"#984ea3",
"#ff7f00",
"#ffff33",
"#a65628",
"#f781bf",
"#999999"
]);
// Show the areas
svg
.selectAll("mylayers")
.data(stackedData)
.enter()
.append("path")
.style("fill", function(d) {
name = mygroups[d.key - 1];
return color(name);
})
.attr(
"d",
d3
.area()
.x(function(d, i) {
return x(d.data.key);
})
.y0(function(d) {
return y(d[0]);
})
.y1(function(d) {
return y(d[1]);
})
);
}
}
Here is where I get the error:
TypeError: Cannot read property 'interactionCount' of undefined
var stackedData = d3
.stack()
.keys(mygroups)
.value(function(d, key) {
return d.values[key].interactionCount;
})(sumstat);
For some reason if I make the list mygroup have one less element in the array, I don't get this error. BUT, my chart doesn't come out looking right.
I've followed the guide word for word line by line and I have no problems replicating the chart. But, when using my own data, I run into issues. Here is the json data:
[{"interactionCount":0,"time":951,"pod":"POD2","client":"C1"},{"interactionCount":6,"time":951,"pod":"POD2","client":"C2"},{"interactionCount":0,"time":951,"pod":"POD2","client":"C3"},{"interactionCount":14,"time":951,"pod":"POD2","client":"C4"},{"interactionCount":44,"time":951,"pod":"POD2","client":"C5"},{"interactionCount":0,"time":951,"pod":"POD2","client":"C6"},{"interactionCount":8,"time":951,"pod":"POD2","client":"C7"},{"interactionCount":0,"time":951,"pod":"POD2","client":"C8"},{"interactionCount":5,"time":951,"pod":"POD2","client":"C9"},{"interactionCount":2,"time":951,"pod":"POD2","client":"C10"},{"interactionCount":0,"time":951,"pod":"POD2","client":"C11"},{"interactionCount":13,"time":951,"pod":"POD2","client":"C12"},{"interactionCount":6,"time":951,"pod":"POD2","client":"C13"},{"interactionCount":0,"time":951,"pod":"POD2","client":"C14"},{"interactionCount":6,"time":951,"pod":"POD2","client":"C15"}]
I was thinking maybe the error was when interactionCount was 0. This is not a problem. I did test this out.
Although I'm following the guide line by line. What am I doing wrong to receive the error?
NOTE my data is json data. The user uses CSV data. Could this be my problem?

D3.js 4 histogram with JSON from AJAX

With help from https://bl.ocks.org histogram example I try to create a histogram with JSON from AJAX.
It seems like my data is not suitable for the histogram() function.
My Data in dev tools (top = my data; bottom = bins from the histogram):
My data is not in histogram bins. The array objects are missing.
Here are the data from bl.ocks.org working example:
...and the bins from histogram from bl.ocks.org example:
You can see it clearly. In my experiment, the data is not in the bins. In the working example of bl.ocks.org you can see the additional objects as an array from index 1 to 13 in the histogram bins.
Here is my full source code:
$(function () {
var updateStatistic = function () {
var dateFrom = $('#date_from').val();
var dateTo = $('#date_to').val();
var parseDate = d3.timeParse('%Y-%m-%d %H:%M:%S'), formatCount = d3.format(',.0f');
var margin = {top: 10, right: 10, bottom: 20, left: 10},
width = 1800 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var dataset = [];
d3.json('/statistic-sizearchive/' + dateFrom + '/' + dateTo, function (data) {
dataset = data.sizeArchive;
dataset.columns = ['date'];
var datetimeFrom = parseDate(dataset[0].archive_time_sql);
var datetimeTo = parseDate(dataset[dataset.length - 1].archive_time_sql);
$(dataset).each(function (index, element) {
element.date = parseDate(element.archive_time_sql);
delete element.archive_time_sql;
});
console.log(dataset);
var x = d3.scaleTime()
.domain([datetimeFrom, datetimeTo])
.rangeRound([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var histogram = d3.histogram()
.value(function (d) {
return d.length;
})
.domain(x.domain())
.thresholds(x.ticks(d3.timeWeek));
var bins = histogram(dataset);
console.log(bins);
y.domain([0, d3.max(bins, function (d) {
return d.length;
})]);
/*
* ### SVG
*/
var svg = d3.select('#statistic_size_archive').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 + ")");
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
var bar = svg.selectAll(".bar")
.data(bins)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function (d) {
return "translate(" + x(d.x0) + "," + y(d.length) + ")";
})
;
bar.append("rect")
.attr("x", 1)
.attr("width", function (d) {
return x(d.x1) - x(d.x0); // x(d.x1) - x(d.x0) - 1
})
.attr("height", function (d) {
return height - y(d.length); // height - y(d.length)
});
bar.append("text")
.attr("dy", ".75em")
.attr("y", 0)
.attr("x", function (d) {
return (x(d.x1) - x(d.x0)) / 2;
})
.attr("text-anchor", "middle")
.text(function (d) {
return formatCount(d.length);
});
});
};
updateStatistic();
$('button#update_statistic').click(function () {
updateStatistic();
});
});
I do not see anything that I'm doing wrong.
Without your actual data, I'm not able to test this code... however, it appears that your histogram call function is returning the wrong value from the data. Instead of returning d.length, shouldn't the code be:
var histogram = d3.histogram()
.value(function (d) {
return d.date;
})
...
This way, the histogram will put each data point into a bin determined by its date?

D3 How to update the chart after selection from drop down menu with new data

I'm building a waterfall chart in D3. When the page will load, it will render the default page but user will have choice to select different
'Company' and 'Year' from the drop down menu. I have been able to create the chart what I want. But when I select any different Company or Year, D3 adds another chart on top of the existing instead of replacing it and thats because I'm targeting a particular div / svg from the HTML. How can I use D3 to update the chart with new data instead add another one of top? And if I can have that movement of chart bars with transition, that will be awesome.
HTML is a simple svg:
<svg class="chart"></svg>
Here is the function to create the chart which I call when Ajax call is successful:
function waterfallChart (dataset) {
var data = [];
for (var key in dataset[0]) {
data.push({
name: key,
value: dataset[0][key]
})
}
var margin = {top: 20, right: 30, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
padding = 0.3;
var x = d3.scaleBand()
.domain(data.map(function(d) {
return d.name
}))
.range([0, width])
.padding(padding);
var y = d3.scaleLinear()
.range([height, 0]);
var xAxis = d3.axisBottom(x)
var yAxis = d3.axisLeft(y)
.tickFormat(function(d) {
return dollarFormatter(d);
});
var chart = d3.select(".chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var cumulative = 0;
for (var i = 0; i < data.length; i++) {
data[i].start = cumulative;
cumulative += data[i].value;
data[i].end = cumulative;
data[i].class = (data[i].value >= 0) ? 'positive' : 'negative'
}
data.push({
name: 'Total',
end: cumulative,
start: 0,
class: 'total'
});
x.domain(data.map(function(d) {
return d.name;
}));
y.domain([0, d3.max(data, function(d) {
return d.end;
})]);
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
chart.append("g")
.attr("class", "y axis")
.call(yAxis);
var bar = chart.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", function(d) {
return "bar " + d.class
})
.attr("transform", function(d) {
return "translate(" + x(d.name) + ",0)";
});
bar.append("rect")
.attr("y", function(d) {
return y(Math.max(d.start, d.end));
})
.attr("height", function(d) {
return Math.abs(y(d.start) - y(d.end));
})
.attr("width", x.bandwidth());
bar.append("text")
.attr("x", x.bandwidth() / 2)
.attr("y", function(d) {
return y(d.end) + 5;
})
.attr("dy", function(d) {
return ((d.class == 'negative') ? '-' : '') + ".75em"
})
.text(function(d) {
return dollarFormatter(d.end - d.start);
});
bar.filter(function(d) {
return d.class != "total"
}).append("line")
.attr("class", "connector")
.attr("x1", x.bandwidth() + 5)
.attr("y1", function(d) {
return y(d.end)
})
.attr("x2", x.bandwidth() / (1 - padding) - 5)
.attr("y2", function(d) {
return y(d.end)
})
function dollarFormatter(n) {
n = Math.round(n);
var result = n;
if (Math.abs(n) > 1000) {
result = Math.round(n/1000) + 'B';
}
return '$ ' + result;
}
}
Here is code where I have event listener and on selection it will run the above function:
$("#airline-selected, #year-selected").change(function chartsData(event) {
event.preventDefault();
var airlineSelected = $('#airline-selected').find(":selected").val();
var yearSelected = $('#year-selected').find(":selected").val();
$.ajax({
url: "{% url 'airline_specific_filtered' %}",
method: 'GET',
data : {
airline_category: airlineSelected,
year_category: yearSelected
},
success: function(dataset){
waterfallChart(dataset)
},
error: function(error_data){
console.log("error")
console.log(error_data)
}
})
});
You are missing some pretty important things here. If you are going to do updates on your data you need to do a couple things.
Give a key to the data() function. You need to give D3 a way to identify data when you update it so it knows if it should add, remove, or leave existing data. The key does this. For instance you might do something like this:
.data(data, function(d) { return d.name })
Now d3 will be able to tell you data items apart assuming d.name is a unique identifier.
You need an exit() for data that is removed during update. You need to save the data joined selection so you can call enter and exit on it:
var bar = chart.selectAll(".bar")
.data(data, function(d) { return d.name})
now you can call: bar.exit().remove() to get rid of deleted items and bar.enter() to add items.
You need to make a selection that hasn't had enter() called on it to update attributes.
Probably more a matter of style, but you should set up the SVG and margins outside the update function since they state the same. You can still update the axis and scales by calling the appropriate functions in the update.
The code you posted is a little hard for other people to run — you'll always get better faster answers if you post code that has been reduced to the main problem and that others can run without needing access to offsite data or apis.
Here's an example that updates on a setInterval between two data sets based on your code. But you should also look at the General Update Patterns - they are very simple but have almost everything you need to know. (https://bl.ocks.org/mbostock/3808234)
dataset = [
{name: "Albert", start: 0, end:220},
{name: "Mark", start: 0, end:200},
{name: "Søren", start: 0, end:100},
{name: "Immanuel", start: 0, end:60},
{name: "Michel", start: 0, end:90},
{name: "Jean Paul", start: 0, end: 80}
]
dataset2 = [
{name: "Albert", start: 0, end:20},
{name: "Immanuel", start:0, end:220},
{name: "Jaques", start: 0, end:100},
{name: "Gerhard", start:0 , end:50},
{name: "Søren", start: 0, end:150},
{name: "William", start: 0, end: 180}
]
var margin = {
top: 10,
right: 30,
bottom: 30,
left: 40
},
width = 400 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom,
padding = 0.3;
var chart = d3.select(".chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand()
.range([0, width])
.padding(padding);
var y = d3.scaleLinear()
.range([height, 0])
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
chart.append("g")
.attr("class", "y axis")
var currentData = dataset
waterfallChart(currentData)
setInterval(function() {
currentData = currentData === dataset ? dataset2 : dataset
waterfallChart(currentData)
}, 3000)
function waterfallChart(data) {
var t = d3.transition()
.duration(750)
x.domain(data.map(function(d) {
return d.name
}))
y.domain([0, d3.max(data, function(d) {
return d.end
})])
var xAxis = d3.axisBottom(x)
var yAxis = d3.axisLeft(y)
d3.select('g.x').transition(t).call(xAxis)
d3.select('g.y').call(yAxis)
var bar = chart.selectAll(".bar")
.data(data, function(d) {
return d.name
})
// ENTER -- ADD ITEMS THAT ARE NEW IN DATA
bar.enter().append("g")
.attr("transform", function(d) {
return "translate(" + x(d.name) + ",0)"
})
.attr("class", 'bar')
.append("rect")
.attr("y", function(d) {
return y(Math.max(d.start, d.end));
})
.attr("height", function(d) {
return Math.abs(y(d.start) - y(d.end));
})
.attr("width", x.bandwidth())
// UPDATE EXISTING ITEMS
chart.selectAll(".bar")
.transition(t)
.attr("transform", function(d) {
return "translate(" + x(d.name) + ",0)"
})
.select('rect')
.attr("y", function(d) {
return y(Math.max(d.start, d.end))
})
.attr("height", function(d) {
return Math.abs(y(d.start) - y(d.end))
})
.attr("width", x.bandwidth())
// REMOVE ITEMS DELETED FROM DATA
bar.exit().remove()
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg class="chart"></svg>

getting error with d3 (V4) Stacked Bar Chart

Getting error while creating Stacked Bar Chart using D3 JS in a Angular 2 application,
here is the code,
//data
var data = [
{ month: 'Jan', A: 20, B: 5, C: 10 },
{ month: 'Feb', A: 30, B: 10, C: 20 }
];
var xData = ["A", "B", "C"];
var margin = { top: 20, right: 50, bottom: 30, left: 0 },
width = 350 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var x = d3.scaleBand()
.range([0, width])
.padding(0.35);
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal(d3.schemeCategory20);
var xAxis = d3.axisBottom(x);
var svg = d3.select("#chart").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 + ")");
var dataIntermediate = xData.map(function (c) {
return data.map(function (d) {
return { x: d.month, y: d[c] };
});
});
var dataStackLayout = d3.stack().keys([dataIntermediate]);
x.domain(dataStackLayout[0].map(function (d) {
return d.x;
}));
y.domain([0,
d3.max(dataStackLayout[dataStackLayout.length - 1],
function (d) { return d.y0 + d.y; })
])
.nice();
var layer = svg.selectAll(".stack")
.data(dataStackLayout)
.enter().append("g")
.attr("class", "stack")
.style("fill", function (d, i) {
return color(i);
});
layer.selectAll("rect")
.data(function (d) {
return d;
})
.enter().append("rect")
.attr("x", function (d) {
return x(d.x);
})
.attr("y", function (d) {
return y(d.y + d.y0);
})
.attr("height", function (d) {
return y(d.y0) - y(d.y + d.y0);
})
.attr("width", x.range());
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
Error are,
(51,41): error TS7017: Index signature of object type implicitly has an 'any' type.
# below line,
return { x: d.month, y: d[c] };
(76,19): error TS2345: Argument of type '(this: BaseType, d: {}) => {}' is not assignable to parameter of type 'ValueFn'.
Type '{}' is not assignable to type '{}[]'.
Property 'find' is missing in type '{}'.
# below line,
var dataStackLayout = d3.stack().keys([dataIntermediate]);
im using same example to implement stacked barchart in angular2.
i think your issue is
var dataStackLayout = d3.stack().keys([dataIntermediate]);
dataStackedLayout should be array instead of function.
were you able to resolve this issue yet?

Wiring events for reusable d3 time slider

I am trying to convert this time slider d3 block to a reusable module. As you can see in jsfiddle, the brush event is not being called. How do I wire up d3 brush event for this module correctly?
Here is what I have so far
jsfiddle link
Code:
(function () {
"use strict";
//============================================================
// Public Variables with Default Settings
//------------------------------------------------------------
var width = null;
var height = null;
var margin = {
top: 5,
right: 5,
bottom: 5,
left: 5
}
var timeScale = d3.time.scale();
var formatDate = d3.time.format("%b %d");
var startingValue = new Date('2012-03-20');
//Private variables
var brush = d3.svg.brush()
.x(timeScale)
.extent([startingValue, startingValue])
.on("brush", slider.brushed);
function slider(selection) {
selection.each(function(data) {
console.log(width, height);
timeScale.range([0, width + margin.left + margin.right]);
var container = d3.select(this).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 + ")");
container.append("g")
.attr("class", "x axis")
// put in middle of screen
.attr("transform", "translate(0," + height / 2 + ")")
// inroduce axis
.call(d3.svg.axis()
.scale(timeScale)
.orient("bottom")
.tickFormat(function(d) {
return formatDate(d);
})
.tickSize(0)
.tickPadding(12)
.tickValues([timeScale.domain()[0], timeScale.domain()[1]]))
.select(".domain")
.select(function() {
return this.parentNode.appendChild(this.cloneNode(true));
})
.attr("class", "halo");
var slider = container.append("g")
.attr("class", "slider")
.call(brush);
slider.selectAll(".extent,.resize")
.remove();
slider.select(".background")
.attr("height", height);
var handle = slider.append("g")
.attr("class", "handle")
handle.append("path")
.attr("transform", "translate(0," + height / 2 + ")")
.attr("d", "M 0 -20 V 20")
handle.append('text')
.text(startingValue)
.attr("transform", "translate(" + (-18) + " ," + (height / 2 - 25) + ")");
slider
.call(brush.event)
function brushed() {
var value = brush.extent()[0];
if (d3.event.sourceEvent) { // not a programmatic event
value = timeScale.invert(d3.mouse(this)[0]);
console.log(d3.mouse(this)[0], value);
brush.extent([value, value]);
}
handle.attr("transform", "translate(" + timeScale(value) + ",0)");
handle.select('text').text(formatDate(value));
}
});
}
//============================================================
// Expose Public Variables
//------------------------------------------------------------
slider.margin = function(_) {
if (!arguments.length) return margin;
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
return slider;
};
slider.width = function(_) {
if (!arguments.length) return width;
width = _;
return slider;
};
slider.height = function(_) {
if (!arguments.length) return height;
height = _;
return slider;
};
slider.startingValue = function(_) {
if (!arguments.length) return startingValue;
startingValue = _;
return slider;
};
slider.formatDate = function(_) {
if (!arguments.length) return formatDate;
formatDate = _;
return slider;
};
slider.timeScale = function(_) {
if (!arguments.length) {
timeScale
.domain([new Date('2012-01-02'), new Date('2013-01-01')])
.clamp(true);
return timeScale;
}
timeScale = _;
return slider;
};
// create slider
d3.select('#year-slider').call(slider.width(500).height(200));
})();
Move the event handler into the scope of the slider instance:
var brush = d3.svg.brush()
.x(timeScale)
.extent([startingValue, startingValue]);
function slider(selection) {
selection.each(function(data) {
...
slider
.call(brush.event)
brush.on("brush", brushed); //<-- in the scope of the slider instance
function brushed() {
var value = brush.extent()[0];
if (d3.event.sourceEvent) { // not a programmatic event
value = timeScale.invert(d3.mouse(this)[0]);
console.log(d3.mouse(this)[0], value);
brush.extent([value, value]);
}
handle.attr("transform", "translate(" + timeScale(value) + ",0)");
handle.select('text').text(formatDate(value));
}
});
Updated example.

Resources