Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@eesur
Created June 19, 2015 13:28
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 eesur/008ec2fc3d587e883e02 to your computer and use it in GitHub Desktop.
Save eesur/008ec2fc3d587e883e02 to your computer and use it in GitHub Desktop.
d3 | legend scroll

Testing legend with scroll for an overtly long legend. Test has simple fade out of lines on click of legend item, and moves the legend on roll over of bar lines. Use the slider to increase/decrease the lines and keys in legend.

(function() {
'use strict';
//************************************************************
// Set up variables
//************************************************************
var data = [];
var items = 9; // data items (used by slider)
function addData(n) {
for (var i=0; i<=n; i++) {
data.push({value: _.random(10,200), ref: i+1});
}
}
var dataReference = [];
var n = 0, // set initial value
w = 800, // width
h = 200; // height
var c20 = d3.scale.category20();
// add yScale
var yScale = d3.scale.linear()
.domain([0, 200])
.range([h, 0]);
// -----------------------------
function createLines(selection) {
selection
.append('line')
.attr({
x1: function(d, i) { return i *20 +10; },
y1: function(d) { return yScale(d.value); },
x2: function(d, i) { return i *20 +10; },
y2: yScale(0),
id: function(d,i) { return 'line-' + i; },
class: 'line'
})
.style({
'stroke-width': 5,
stroke: function(d,i) {
if (i < 10) {
return c20(i);
} else {
var j = i % 20;
return c20(j);
}
}
})
.on('mouseover', function(d,i) {
// move legend on mouse over
moveLegend('li-' + i);
// update n for the buttons
n = i;
});
}
var svg = d3.select('#chart')
.append('svg')
.attr({
width: w,
height: h
});
//************************************************************
// Legend
//************************************************************
function renderLegend(data) {
// create legend
var legend = d3.eesur.legend()
.dataKeys(dataReference);
// event to update yScale when toggling line series
legend.on('toggleLegend', function(d, i) {
// console.log('toggleLegend: ' + d, i);
// reference line via it's id
var line = d3.select('#line-' + i);
// reference legend item via it's ID e.g. 'li-0', 'li-1' etc
var liID = 'li-' + i;
// toggle item in array
if (line.classed('legend-active')) {
// toggle line
line
.style('opacity', 1)
.classed('legend-active', false);
// toggle legend item
d3.select('#' + liID).style('opacity', 1);
} else {
// fade line
line
.style('opacity', 0.2)
.classed('legend-active', true);
// fade legend item
d3.select('#' + liID).style('opacity', 0.2);
}
});
d3.select('#legend-list')
.call(legend);
}
//************************************************************
// navigation for legend
//************************************************************
var legendNavContainer = d3.select('#legend');
function addLegendNav() {
legendNavContainer
.append('button')
.attr({
class: 'legendNav',
id: 'legendPrev'
})
.html('&lsaquo;');
legendNavContainer
.append('button')
.attr({
class: 'legendNav',
id: 'legendNext'
})
.html('&rsaquo;');
}
// controls to navigate back and forward
function legendControls(data, index) {
var nextBtn = d3.select('#legendNext');
var prevBtn = d3.select('#legendPrev');
d3.select('#legendNext').on('click', function() {
// ensure buttons can't be clicked where there are no items
if (n < data.length-4) {
// ensure buttons are 'visually' active
nextBtn.style('opacity', 1);
prevBtn.style('opacity', 1);
// update value of counter
n++;
// set index to the value of i
index = n;
console.log('value of i NEXT: ' + n);
moveLegend('li-' + index);
} else {
// set n to be value not including the visible legend items
// so that the prev button can be clicked with staggering
n = data.length-4;
// fade button out for user feedback
nextBtn.style('opacity', 0.2);
}
});
d3.select('#legendPrev').on('click', function() {
if (n >= 1) {
prevBtn.style('opacity', 1);
nextBtn.style('opacity', 1);
n--;
index = n;
console.log('value of i PREV: ' + n);
moveLegend('li-' + index);
} else {
prevBtn.style('opacity', 0.2);
}
});
}
// offset items in list via their id
function moveLegend(reference) {
var list = document.getElementById('legend-list'),
targetLi = document.getElementById(reference); // id tag of the <li> element
list.scrollLeft = (targetLi.offsetLeft - 80);
console.log(reference + ': ' +targetLi.offsetLeft);
}
// -----------------------------
// render
function render(newDataAmount) {
// remove old items
d3.selectAll('.line').remove();
d3.selectAll('.legendNav').remove();
d3.select('#legend-list').remove();
// update data
data = [];
addData(items);
// give some fake key values to the dummy data
dataReference = data.map(function(d, i) {
return 'bar ' + i;
});
// create the lines
var lines = svg.selectAll('line')
.data(data)
.enter()
.call(createLines);
// create legend
renderLegend(data);
// only show the controls if needed
if (data.length > 4) {
addLegendNav();
legendControls(dataReference, 1);
}
}
render(items);
// -----------------------------
// slider code to change number of data items/lines
// https://github.com/turban/d3.slider
// ensure slider text is showing current render values
d3.select('#slider-text-items').text(items);
// update items
d3.select('#slider-items').call(d3.slider().value(items).min(0).max(40).on('slide', function(evt, value) {
items = d3.round(value); // ensure whole number
d3.select('#slider-text-items').text(items + 1); // update the text to show value
render(items); // update with new amount of items
}));
})();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>d3 legend | scrollable legend</title>
<meta name="author" content="Sundar Singh | eesur.com">
<link rel="stylesheet" type="text/css" href="https://rawgit.com/turban/d3.slider/master/d3.slider.css">
<link rel="stylesheet" href="main.css">
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.9.3/lodash.min.js"></script>
</head>
<body>
<header>
<p>Crazy test for when legend has to many items for viewport. Next and Prev buttons will only show if over four items/lines</p>
<p>Number of items: <span id="slider-text-items">9</span></p>
<div id="slider-items"></div>
</header>
<section id="container">
<section id="chart"></section>
<aside id="legend"></aside>
</section>
<script src="https://rawgit.com/turban/d3.slider/master/d3.slider.js"></script>
<script src="legend_component.js" charset="utf-8"></script>
<script src="d3_code_legend.js" charset="utf-8"></script>
</body>
</html>
(function() {
'use strict';
d3.eesur = {}; // namespace
d3.eesur.legend = function() {
var container = 'legend', // ID of DIV container
list = 'legend-list', // ID for UL
dataKeys = []; // array of key values from JSON data
var dispatch = d3.dispatch('toggleLegend');
var c20 = d3.scale.category20();
var legend = function() {
// ensure container is visible
d3.select('#' + container).transition().duration(1000).style('opacity', 1);
var legend = d3.select('#' + container + ' ul');
// create legend container if doesn't exist
if (legend.empty()) {
legend = d3.select('#' + container)
.attr('class', 'legend_wrap');
legend
.append('ul')
.attr('id', list);
// add the legend items
var legendList = d3.select('#' + list)
.selectAll('li')
.data(dataKeys);
legendList
.enter()
.append('li')
.attr('class', 'legend-item')
.attr('id', function(d, i) {
return 'li-' + i;
})
.on('click', function(d,i) {
return dispatch.toggleLegend(d, i);
});
legendList.append('span')
.attr('class', 'legend-line')
.style('color', function(d,i) { return c20(i); })
.html('&mdash; ');
legendList.append('span')
.attr('class', 'legend-title')
.style('color', '#7AC143;')
.html(function(d, i) { return dataKeys[i]; });
}
};
legend.container = function(value) {
if (!arguments.length) return container;
container = value;
return this;
};
legend.list = function(value) {
if (!arguments.length) return list;
list = value;
return this;
};
legend.dataKeys = function(value) {
if (!arguments.length) return dataKeys;
dataKeys = value;
return this;
};
d3.rebind(legend, dispatch, 'on');
return legend;
};
}());
@import url(http://fonts.googleapis.com/css?family=Source+Code+Pro:400,600);
html, body {
height: 100%;
}
body {
font-family: "Source Code Pro", Consolas, monaco, monospace;
line-height: 1.5;
font-weight: 400;
background-color: #130C0E;
color: #7AC143;
padding: 10px;
}
p {
max-width: 600px;
}
button {
font-family: "Source Code Pro", Consolas, monaco, monospace;
font-size: 14px;
background: #130C0E;
color: #FDBB30;
border: none;
outline:none;
padding: 4px 8px;
letter-spacing: 1px;
}
button:hover {
color: #EE3124;
}
#container {
text-align: center;
white-space: nowrap;
width: 800px;
margin: 0 auto;
margin-top: 20px;
}
#chart {
height: 200px;
margin-bottom: 40px;
}
.legendNav {
font-size: 24px;
display: inline-block;
}
.legendNav:hover {
font-weight: bold;
}
#legendPrev {
position: absolute;
left: 20px;
top: -10px;
}
#legendNext {
position: absolute;
right: 20px;
top: -10px;
}
.legend-group{
display: inline-block;
width: 100px;
}
#legend {
position: relative;
padding-top: 5px;
font-size: 13px;
bottom: 5px;
text-align: left;
padding: 0px;
}
#legend ul {
list-style: none;
text-align: center;
height: 24px;
padding-left: 0;
min-width: 300px;
max-width: 640px;
margin: 0 auto;
overflow: hidden;
}
#legend li {
padding-right: 0;
cursor: pointer;
font-size: 13px;
display: inline-block;
width: 160px;
overflow: hidden;
letter-spacing: 1px;
}
#legend li span {
padding: 2px 4px;
pointer-events: none;
}
.legend-line {
margin-left: 8px;
vertical-align: right;
font-size: 14px;
line-height: 13px;
}
#slider-items, #slider-rows {
margin: 10px 0;
max-width: 300px;
}
.d3-slider {
border: 1px solid #7AC143;
}
.d3-slider-handle {
border: none;
background: #7AC143;
}
.d3-slider-handle:hover {
border: 1px solid #7AC143;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment