Я адаптировал многострочный график с легендой и осью, который правильно отображается на сайте bl.ocks.org (http://bl.ocks.org/Matthew-Weber/5645518). Легенда реорганизуется, когда вы выбираете другой тип из выпадающего поля. На моей адаптации, когда легенда реорганизуется, элементы начинают перекрывать друг друга, когда выбираются некоторые типы. Также оси опираются друг на друга. В оригинальном коде используются подсказки, но я его не проверял.
// original author's code http://bl.ocks.org/Matthew-Weber/5645518;
//set the margins
var margin = {
top: 50,
right: 160,
bottom: 80,
left: 50
},
width = 900 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//set dek and head to be as wide as SVG
d3.select('#dek')
.style('width', width + 'px');
d3.select('#headline')
.style('width', width + 'px');
//write out your source text here
var sourcetext = "xxx";
// set the type of number here, n is a number with a comma, .2% will get you a percent, .2f will get you 2 decimal points
var NumbType = d3.format(",");
// color array
var bluescale4 = ["red", "blue", "green", "orange", "purple"];
//color function pulls from array of colors stored in color.js
var color = d3.scale.ordinal().range(bluescale4);
//defines a function to be used to append the title to the tooltip.
var maketip = function(d) {
var tip = '<p class="tip3">' + d.name + '<p class="tip1">' + NumbType(d.value) + '</p> <p class="tip3">' + formatDate(d.date) + '</p>';
return tip;
}
//define your year format here, first for the x scale, then if the date is displayed in tooltips
var parseDate = d3.time.format("%Y-%m-%d").parse;
var formatDate = d3.time.format("%b %d, '%y");
//create an SVG
var svg = d3.select("#graphic").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 + ")");
//make a rectangle so there is something to click on
svg.append("svg:rect")
.attr("width", width)
.attr("height", height)
.attr("class", "plot"); //#fff
// force data to update when menu is changed
var menu = d3.select("#menu select")
.on("change", change);
//suck in the data, store it in a value called formatted, run the redraw function
d3.csv("/sites/default/d3_files/d3-provinces/statistics-april-15-2.csv", function(data) {
formatted = data;
redraw();
});
d3.select(window)
.on("keydown", function() {
altKey = d3.event.altKey;
})
.on("keyup", function() {
altKey = false;
});
var altKey;
// set terms of transition that will take place
// when a new type (Death etc.)indicator is chosen
function change() {
d3.transition()
.duration(altKey ? 7500 : 1500)
.each(redraw);
} // end change
// REDRAW all the meat goes in the redraw function
function redraw() {
// create data nests based on type indicator (series)
var nested = d3.nest()
.key(function(d) {
return d.type;
})
.map(formatted)
// get value from menu selection
// the option values are set in HTML and correspond
//to the [type] value we used to nest the data
var series = menu.property("value");
// only retrieve data from the selected series, using the nest we just created
var data = nested[series];
// for object constancy we will need to set "keys", one for each type of data (column name) exclude all others.
color.domain(d3.keys(data[0]).filter(function(key) {
return (key !== "date" && key !== "type");
}));
var linedata = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {
name: name,
date: parseDate(d.date),
value: parseFloat(d[name], 10)
};
})
};
});
//make an empty variable to stash the last values into so we can sort the legend // do we need to sort it?
var lastvalues = [];
//setup the x and y scales
var x = d3.time.scale()
.domain([
d3.min(linedata, function(c) {
return d3.min(c.values, function(v) {
return v.date;
});
}),
d3.max(linedata, function(c) {
return d3.max(c.values, function(v) {
return v.date;
});
})
])
.range([0, width]);
var y = d3.scale.linear()
.domain([
d3.min(linedata, function(c) {
return d3.min(c.values, function(v) {
return v.value;
});
}),
d3.max(linedata, function(c) {
return d3.max(c.values, function(v) {
return v.value;
});
})
])
.range([height, 0]);
//will draw the line
var line = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.value);
});
//create and draw the x axis - need to clear the existing axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickPadding(8)
.ticks(10);
//create and draw the y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(0 - width)
.tickPadding(8);
svg.append("svg:g")
.attr("class", "x axis");
svg.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(" + (0) + ",0)")
.call(yAxis);
//bind the data
var thegraph = svg.selectAll(".thegraph")
.data(linedata)
//append a g tag for each line and set of tooltip circles and give it a unique ID based on the column name of the data
var thegraphEnter = thegraph.enter().append("g")
.attr("class", "thegraph")
.attr('id', function(d) {
return d.name + "-line";
})
.style("stroke-width", 2.5)
.on("mouseover", function(d) {
d3.select(this) //on mouseover of each line, give it a nice thick stroke // works
.style("stroke-width", '6px');
var selectthegraphs = $('.thegraph').not(this); //select all the rest of the lines, except the one you are hovering on and drop their opacity
d3.selectAll(selectthegraphs)
.style("opacity", 0.2);
var getname = document.getElementById(d.name); //use get element cause the ID names have spaces in them - not working
var selectlegend = $('.legend').not(getname); //grab all the legend items that match the line you are on, except the one you are hovering on
d3.selectAll(selectlegend) // drop opacity on other legend names
.style("opacity", .2);
d3.select(getname)
.attr("class", "legend-select"); //change the class on the legend name that corresponds to hovered line to be bolder
}) // end of mouseover
.on("mouseout", function(d) { //undo everything on the mouseout
d3.select(this)
.style("stroke-width", '2.5px');
var selectthegraphs = $('.thegraph').not(this);
d3.selectAll(selectthegraphs)
.style("opacity", 1);
var getname = document.getElementById(d.name);
var getname2 = $('.legend[fakeclass="fakelegend"]')
var selectlegend = $('.legend').not(getname2).not(getname);
d3.selectAll(selectlegend)
.style("opacity", 1);
d3.select(getname)
.attr("class", "legend");
});
//actually append the line to the graph
thegraphEnter.append("path")
.attr("class", "line")
.style("stroke", function(d) {
return color(d.name);
})
.attr("d", function(d) {
return line(d.values[0]);
})
.transition()
.duration(2000)
.attrTween('d', function(d) {
var interpolate = d3.scale.quantile()
.domain([0, 1])
.range(d3.range(1, d.values.length + 1));
return function(t) {
return line(d.values.slice(0, interpolate(t)));
};
});
//then append some 'nearly' invisible circles at each data point
thegraph.selectAll("circle")
.data(function(d) {
return (d.values);
})
.enter()
.append("circle")
.attr("class", "tipcircle")
.attr("cx", function(d, i) {
return x(d.date)
})
.attr("cy", function(d, i) {
return y(d.value)
})
.attr("r", 3) // was 12
.style('opacity', .2)
.attr("title", maketip);
//append the legend
var legend = svg.selectAll('.legend')
.data(linedata);
var legendEnter = legend
.enter()
.append('g')
.attr('class', 'legend')
.attr('id', function(d) {
return d.name;
})
.on('click', function(d) { //onclick function to toggle off the lines
if ($(this).css("opacity") == 1) {
//uses the opacity of the item clicked on to determine whether to turn the line on or off
var elemented = document.getElementById(this.id + "-line"); //grab the line that has the same ID as this point along w/ "-line"
//use get element cause ID has spaces
d3.select(elemented)
.transition()
.duration(1000)
.style("opacity", 0)
.style("display", 'none');
d3.select(this)
.attr('fakeclass', 'fakelegend')
.transition()
.duration(1000)
.style("opacity", .2);
} else {
var elemented = document.getElementById(this.id + "-line");
d3.select(elemented)
.style("display", "block")
.transition()
.duration(1000)
.style("opacity", 1);
d3.select(this)
.attr('fakeclass', 'legend')
.transition()
.duration(1000)
.style("opacity", 1);
}
});
//create a scale to pass the legend items through // this is broken for some types
var legendscale = d3.scale.ordinal()
.domain(lastvalues)
.range([0, 30, 60, 90, 120, 150, 180, 210]);
//actually add the circles to the created legend container
legendEnter.append('circle')
.attr('cx', width + 20) // cx=width+50 made circle overlap text
.attr('cy', function(d) {
var newScale = (legendscale(d.values[d.values.length - 1].value) + 20);
return newScale;
})
.attr('r', 7)
.style('fill', function(d) {
return color(d.name);
});
//add the legend text
legendEnter.append('text')
.attr('x', width + 35) // is this an issue?
.attr('y', function(d) {
return legendscale(d.values[d.values.length - 1].value);
})
.text(function(d) {
return d.name;
});
// set variable for updating visualization
var thegraphUpdate = d3.transition(thegraph);
// change values of path and then the circles to those of the new series
thegraphUpdate.select("path")
.attr("d", function(d, i) {
lastvalues[i] = d.values[d.values.length - 1].value;
lastvalues.sort(function(a, b) {
return b - a
});
legendscale.domain(lastvalues);
return line(d.values);
// }
});
thegraphUpdate.selectAll("circle")
.attr("title", maketip) // displays HTML but not circle
.attr("cy", function(d, i) {
return y(d.value)
})
.attr("cx", function(d, i) {
return x(d.date)
});
// and now for legend items
var legendUpdate = d3.transition(legend);
legendUpdate.select("circle")
.attr('cy', function(d, i) {
return legendscale(d.values[d.values.length - 1].value);
});
legendUpdate.select("text")
.attr('y', function(d) {
return legendscale(d.values[d.values.length - 1].value);
});
d3.transition(svg).select(".x.axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
//make my tooltips work
$('circle').tipsy({
opacity: .9,
gravity: 'n',
html: true
});
//end of the redraw function
}
svg.append("svg:text")
.attr("text-anchor", "start")
.attr("x", 0 - margin.left)
.attr("y", height + margin.bottom - 10)
.text(sourcetext)
.attr("class", "source");
Мой адаптированный код (включая множество сообщений console.log) находится в jsfiddle https://jsfiddle.net/pwarwick43/13fpn567/2/
Я начинаю думать, что проблема может быть в версии d3 или jquery. Кто-нибудь получил предложения по этому поводу?