mirror of https://github.com/vitalif/dimple
Bar Popups
parent
70521c8e32
commit
1c41f24670
|
@ -1,5 +1,5 @@
|
||||||
<div id="chartContainer">
|
<div id="chartContainer">
|
||||||
<script src="http://d3js.org/d3.v3.min.js"></script>
|
<script src="/lib/d3.v3.min.js"></script>
|
||||||
<script src="/dist/dimple.v1.min.js"></script>
|
<script src="/dist/dimple.v1.min.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var svg = dimple.newSvg("#chartContainer", 590, 400);
|
var svg = dimple.newSvg("#chartContainer", 590, 400);
|
||||||
|
|
|
@ -2,11 +2,29 @@
|
||||||
// License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt"
|
// License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt"
|
||||||
// Source: /src/objects/plot/bar.js
|
// Source: /src/objects/plot/bar.js
|
||||||
dimple.plot.bar = {
|
dimple.plot.bar = {
|
||||||
|
|
||||||
|
// By default the bar series is stacked if there are series categories
|
||||||
stacked: true,
|
stacked: true,
|
||||||
|
|
||||||
|
// The axes which will affect the bar chart - not z
|
||||||
supportedAxes: ["x", "y", "c"],
|
supportedAxes: ["x", "y", "c"],
|
||||||
|
|
||||||
|
// Draw the chart
|
||||||
draw: function (chart, series, duration) {
|
draw: function (chart, series, duration) {
|
||||||
// Get the series data
|
|
||||||
|
// Get self pointer for inner functions
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Clear any hover gubbins before redrawing so the hover markers aren't left behind
|
||||||
|
chart.svg.selectAll(".hoverShapes")
|
||||||
|
.transition()
|
||||||
|
.duration(duration / 4)
|
||||||
|
.style("opacity", 0)
|
||||||
|
.remove();
|
||||||
|
|
||||||
|
// Get the series data
|
||||||
var chartData = series._positionData;
|
var chartData = series._positionData;
|
||||||
|
|
||||||
// If the series is uninitialised create placeholders, otherwise use the existing shapes
|
// If the series is uninitialised create placeholders, otherwise use the existing shapes
|
||||||
var theseShapes = null;
|
var theseShapes = null;
|
||||||
var className = "series" + chart.series.indexOf(series);
|
var className = "series" + chart.series.indexOf(series);
|
||||||
|
@ -27,6 +45,12 @@ dimple.plot.bar = {
|
||||||
.attr("width", function (d) {return (d.xField != null && d.xField.length > 0 ? _helpers.width(d, chart, series) : 0); })
|
.attr("width", function (d) {return (d.xField != null && d.xField.length > 0 ? _helpers.width(d, chart, series) : 0); })
|
||||||
.attr("height", function (d) {return (d.yField != null && d.yField.length > 0 ? _helpers.height(d, chart, series) : 0); })
|
.attr("height", function (d) {return (d.yField != null && d.yField.length > 0 ? _helpers.height(d, chart, series) : 0); })
|
||||||
.attr("opacity", function (d) { return _helpers.opacity(d, chart, series); })
|
.attr("opacity", function (d) { return _helpers.opacity(d, chart, series); })
|
||||||
|
.on("mouseover", function (e) {
|
||||||
|
self.enterEventHandler(e, this, chart, series, duration)
|
||||||
|
})
|
||||||
|
.on("mouseleave", function (e) {
|
||||||
|
self.leaveEventHandler(e, this, chart, series, duration)
|
||||||
|
})
|
||||||
.call(function () {
|
.call(function () {
|
||||||
if (!chart.noFormats) {
|
if (!chart.noFormats) {
|
||||||
this.attr("fill", function (d) { return _helpers.fill(d, chart, series); })
|
this.attr("fill", function (d) { return _helpers.fill(d, chart, series); })
|
||||||
|
@ -62,6 +86,182 @@ dimple.plot.bar = {
|
||||||
|
|
||||||
// Save the shapes to the series array
|
// Save the shapes to the series array
|
||||||
series.shapes = theseShapes;
|
series.shapes = theseShapes;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Handle the mouse enter event
|
||||||
|
enterEventHandler: function (e, shape, chart, series, duration) {
|
||||||
|
|
||||||
|
// The margin between the text and the box
|
||||||
|
const textMargin = 5;
|
||||||
|
// The margin between the ring and the popup
|
||||||
|
const popupMargin = 10;
|
||||||
|
|
||||||
|
// Collect some facts about the highlighted bubble
|
||||||
|
var svg = chart.svg;
|
||||||
|
var selectedShape = d3.select(shape);
|
||||||
|
var x = parseFloat(selectedShape.attr("x"));
|
||||||
|
var y = parseFloat(selectedShape.attr("y"));
|
||||||
|
var width = parseFloat(selectedShape.attr("width"));
|
||||||
|
var height = parseFloat(selectedShape.attr("height"));
|
||||||
|
var opacity = selectedShape.attr("opacity");
|
||||||
|
var fill = selectedShape.attr("fill");
|
||||||
|
|
||||||
|
// Fade the popup stroke mixing the shape fill with 60% white
|
||||||
|
var popupStrokeColor = d3.rgb(
|
||||||
|
d3.rgb(fill).r + 0.6 * (255 - d3.rgb(fill).r),
|
||||||
|
d3.rgb(fill).g + 0.6 * (255 - d3.rgb(fill).g),
|
||||||
|
d3.rgb(fill).b + 0.6 * (255 - d3.rgb(fill).b)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fade the popup fill mixing the shape fill with 80% white
|
||||||
|
var popupFillColor = d3.rgb(
|
||||||
|
d3.rgb(fill).r + 0.8 * (255 - d3.rgb(fill).r),
|
||||||
|
d3.rgb(fill).g + 0.8 * (255 - d3.rgb(fill).g),
|
||||||
|
d3.rgb(fill).b + 0.8 * (255 - d3.rgb(fill).b)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a group for the hover objects
|
||||||
|
var g = svg.append("g")
|
||||||
|
.attr("class", "hoverShapes");
|
||||||
|
|
||||||
|
// Add a highlight around the data point but only if the data point
|
||||||
|
// is big enough to take it. Otherwise the ring interferes with the
|
||||||
|
// hover event
|
||||||
|
if (height > 4 && width > 4) {
|
||||||
|
g.append("rect")
|
||||||
|
.attr("x", x + 1)
|
||||||
|
.attr("y", y + 1)
|
||||||
|
.attr("width", width - 2)
|
||||||
|
.attr("height", height - 2)
|
||||||
|
.attr("fill", "none")
|
||||||
|
.attr("stroke", d3.rgb(fill).darker(0.1))
|
||||||
|
.attr("stroke-width", 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a group for text
|
||||||
|
var t = g.append("g");
|
||||||
|
// Create a box for the popup in the text group
|
||||||
|
var box = t.append("rect");
|
||||||
|
// Get the rows for the text
|
||||||
|
var rows = [];
|
||||||
|
|
||||||
|
// Add the series categories
|
||||||
|
if (series.categoryFields != null && series.categoryFields != undefined && series.categoryFields.length > 0) {
|
||||||
|
series.categoryFields.forEach(function (c, i) {
|
||||||
|
// If the category name and value match don't display the category name
|
||||||
|
rows.push(c + (e.aggField != c ? ": " + e.aggField[i] : ""))
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (series.x._hasCategories()) {
|
||||||
|
// Add the x axis categories
|
||||||
|
series.x.categoryFields.forEach(function (c, i) {
|
||||||
|
// If the category name and value match don't display the category name
|
||||||
|
rows.push(c + (e.xField != c ? ": " + e.xField[i] : ""));
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Add the axis measure value
|
||||||
|
rows.push(series.x.measure + ": " + series.x._getFormat()(e.width));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (series.y._hasCategories()) {
|
||||||
|
// Add the y axis categories
|
||||||
|
series.y.categoryFields.forEach(function (c, i) {
|
||||||
|
rows.push(c + (e.yField != c ? ": " + e.yField[i] : ""));
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Add the axis measure value
|
||||||
|
rows.push(series.y.measure + ":" + series.y._getFormat()(e.height));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (series.c != null && series.c != undefined) {
|
||||||
|
// Add the axis measure value
|
||||||
|
rows.push(series.c.measure+ ": " + series.c._getFormat()(series.c.showPercent ? e.cPct : e.cValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get distinct text rows to deal with cases where 2 axes have the same dimensionality
|
||||||
|
rows = rows.filter(function(elem, pos) {
|
||||||
|
return rows.indexOf(elem) == pos;
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create a text object for every row in the popup
|
||||||
|
t.selectAll(".textHoverShapes").data(rows).enter()
|
||||||
|
.append("text")
|
||||||
|
.text(function (d) { return d; })
|
||||||
|
.style("font-family", "sans-serif")
|
||||||
|
.style("font-size", "10px");
|
||||||
|
|
||||||
|
// The running y value for the text elements
|
||||||
|
var yRunning = 0;
|
||||||
|
// The maximum bounds of the text elements
|
||||||
|
var w = 0;
|
||||||
|
var h = 0;
|
||||||
|
|
||||||
|
// Get the max height and width of the text items
|
||||||
|
t.each(function (d) {
|
||||||
|
w = (this.getBBox().width > w ? this.getBBox().width : w);
|
||||||
|
h = (this.getBBox().width > h ? this.getBBox().height : h);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Position the text relatve to the bubble, the absolute positioning
|
||||||
|
// will be done by translating the group
|
||||||
|
t.selectAll("text")
|
||||||
|
.attr("x", 0)
|
||||||
|
.attr("y", function (d, i) {
|
||||||
|
// Increment the y position
|
||||||
|
yRunning += this.getBBox().height;
|
||||||
|
// Position the text at the centre point
|
||||||
|
return yRunning - (this.getBBox().height / 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Draw the box with a margin around the text
|
||||||
|
box.attr("x", -textMargin)
|
||||||
|
.attr("y", -textMargin)
|
||||||
|
.attr("height", Math.floor(yRunning + textMargin) - 0.5)
|
||||||
|
.attr("width", w + 2 * textMargin)
|
||||||
|
.attr("rx", 5)
|
||||||
|
.attr("ry", 5)
|
||||||
|
.style("fill", popupFillColor)
|
||||||
|
.style("stroke", popupStrokeColor)
|
||||||
|
.style("stroke-width", 2)
|
||||||
|
.style("opacity", 0.95);
|
||||||
|
|
||||||
|
// Shift the popup around to avoid overlapping the svg edge
|
||||||
|
if (x + width + textMargin + popupMargin + w < parseFloat(svg.attr("width"))) {
|
||||||
|
t.attr("transform", "translate(" +
|
||||||
|
(x + width + textMargin + popupMargin) + " , " +
|
||||||
|
(y + (height / 2) - ((yRunning - (h - textMargin)) / 2)) +
|
||||||
|
")");
|
||||||
|
}
|
||||||
|
else if (x - (textMargin + popupMargin + w) > 0) {
|
||||||
|
t.attr("transform", "translate(" +
|
||||||
|
(x - (textMargin + popupMargin + w)) + " , " +
|
||||||
|
(y + (height / 2) - ((yRunning - (h - textMargin)) / 2)) +
|
||||||
|
")");
|
||||||
|
}
|
||||||
|
else if (y - yRunning - textMargin > 10) {
|
||||||
|
t.attr("transform", "translate(" +
|
||||||
|
(x + textMargin) + " , " +
|
||||||
|
(y - yRunning - (h - textMargin)) +
|
||||||
|
")");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
t.attr("transform", "translate(" +
|
||||||
|
(x + textMargin) + " , " +
|
||||||
|
(y + height + 2 * textMargin) +
|
||||||
|
")");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// Handle the mouse leave event
|
||||||
|
leaveEventHandler: function (e, shape, chart, series, duration) {
|
||||||
|
// Clear all hover shapes
|
||||||
|
chart.svg
|
||||||
|
.selectAll(".hoverShapes")
|
||||||
|
.remove();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -88,8 +88,8 @@ dimple.plot.bubble = {
|
||||||
// Handle the mouse enter event
|
// Handle the mouse enter event
|
||||||
enterEventHandler: function (e, shape, chart, series, duration) {
|
enterEventHandler: function (e, shape, chart, series, duration) {
|
||||||
|
|
||||||
// The margin between the edge of the circle and the ring
|
// The margin between the text and the box
|
||||||
const ringMargin = 5;
|
const textMargin = 5;
|
||||||
// The margin between the ring and the popup
|
// The margin between the ring and the popup
|
||||||
const popupMargin = 10;
|
const popupMargin = 10;
|
||||||
|
|
||||||
|
@ -100,22 +100,20 @@ dimple.plot.bubble = {
|
||||||
var cy = parseFloat(selectedShape.attr("cy"));
|
var cy = parseFloat(selectedShape.attr("cy"));
|
||||||
var r = parseFloat(selectedShape.attr("r"));
|
var r = parseFloat(selectedShape.attr("r"));
|
||||||
var opacity = selectedShape.attr("opacity");
|
var opacity = selectedShape.attr("opacity");
|
||||||
|
var fill = selectedShape.attr("fill");
|
||||||
// Color the highlight ring to match the fill of the bubble
|
|
||||||
var ringColor = selectedShape.attr("fill");
|
|
||||||
|
|
||||||
// Fade the popup stroke mixing the shape fill with 60% white
|
// Fade the popup stroke mixing the shape fill with 60% white
|
||||||
var popupStrokeColor = d3.rgb(
|
var popupStrokeColor = d3.rgb(
|
||||||
d3.rgb(ringColor).r + 0.6 * (255 - d3.rgb(ringColor).r),
|
d3.rgb(fill).r + 0.6 * (255 - d3.rgb(fill).r),
|
||||||
d3.rgb(ringColor).g + 0.6 * (255 - d3.rgb(ringColor).g),
|
d3.rgb(fill).g + 0.6 * (255 - d3.rgb(fill).g),
|
||||||
d3.rgb(ringColor).b + 0.6 * (255 - d3.rgb(ringColor).b)
|
d3.rgb(fill).b + 0.6 * (255 - d3.rgb(fill).b)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Fade the popup fill mixing the shape fill with 80% white
|
// Fade the popup fill mixing the shape fill with 80% white
|
||||||
var popupFillColor = d3.rgb(
|
var popupFillColor = d3.rgb(
|
||||||
d3.rgb(ringColor).r + 0.8 * (255 - d3.rgb(ringColor).r),
|
d3.rgb(fill).r + 0.8 * (255 - d3.rgb(fill).r),
|
||||||
d3.rgb(ringColor).g + 0.8 * (255 - d3.rgb(ringColor).g),
|
d3.rgb(fill).g + 0.8 * (255 - d3.rgb(fill).g),
|
||||||
d3.rgb(ringColor).b + 0.8 * (255 - d3.rgb(ringColor).b)
|
d3.rgb(fill).b + 0.8 * (255 - d3.rgb(fill).b)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a group for the hover objects
|
// Create a group for the hover objects
|
||||||
|
@ -126,9 +124,9 @@ dimple.plot.bubble = {
|
||||||
g.append("circle")
|
g.append("circle")
|
||||||
.attr("cx", cx)
|
.attr("cx", cx)
|
||||||
.attr("cy", cy)
|
.attr("cy", cy)
|
||||||
.attr("r", r + ringMargin - 1)
|
.attr("r", r + 4)
|
||||||
.attr("fill", "none")
|
.attr("fill", "none")
|
||||||
.attr("stroke", ringColor)
|
.attr("stroke", fill)
|
||||||
.attr("stroke-width", 2);
|
.attr("stroke-width", 2);
|
||||||
|
|
||||||
// Add a group for text
|
// Add a group for text
|
||||||
|
@ -155,7 +153,7 @@ dimple.plot.bubble = {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Add the axis measure value
|
// Add the axis measure value
|
||||||
rows.push(series.x.measure + ": " + series.x._getFormat()(e.xValue));
|
rows.push(series.x.measure + ": " + series.x._getFormat()(e.cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (series.y._hasCategories()) {
|
if (series.y._hasCategories()) {
|
||||||
|
@ -166,7 +164,7 @@ dimple.plot.bubble = {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Add the axis measure value
|
// Add the axis measure value
|
||||||
rows.push(series.y.measure + ":" + series.y._getFormat()(e.yValue));
|
rows.push(series.y.measure + ":" + series.y._getFormat()(e.cy));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (series.z != null && series.z != undefined) {
|
if (series.z != null && series.z != undefined) {
|
||||||
|
@ -215,10 +213,10 @@ dimple.plot.bubble = {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Draw the box with a margin around the text
|
// Draw the box with a margin around the text
|
||||||
box.attr("x", -ringMargin)
|
box.attr("x", -textMargin)
|
||||||
.attr("y", -ringMargin)
|
.attr("y", -textMargin)
|
||||||
.attr("height", Math.floor(y + ringMargin) - 0.5)
|
.attr("height", Math.floor(y + textMargin) - 0.5)
|
||||||
.attr("width", w + 2 * ringMargin)
|
.attr("width", w + 2 * textMargin)
|
||||||
.attr("rx", 5)
|
.attr("rx", 5)
|
||||||
.attr("ry", 5)
|
.attr("ry", 5)
|
||||||
.style("fill", popupFillColor)
|
.style("fill", popupFillColor)
|
||||||
|
@ -227,12 +225,12 @@ dimple.plot.bubble = {
|
||||||
.style("opacity", 0.95);
|
.style("opacity", 0.95);
|
||||||
|
|
||||||
// Shift the ring margin left or right depending on whether it will overlap the edge
|
// Shift the ring margin left or right depending on whether it will overlap the edge
|
||||||
var overlap = cx + r + ringMargin + popupMargin + w > parseFloat(svg.attr("width"));
|
var overlap = cx + r + textMargin + popupMargin + w > parseFloat(svg.attr("width"));
|
||||||
|
|
||||||
// Translate the shapes to the x position of the bubble (the x position of the shapes is handled)
|
// Translate the shapes to the x position of the bubble (the x position of the shapes is handled)
|
||||||
t.attr("transform", "translate(" +
|
t.attr("transform", "translate(" +
|
||||||
(overlap ? cx - (r + ringMargin + popupMargin + w) : cx + r + ringMargin + popupMargin) + " , " +
|
(overlap ? cx - (r + textMargin + popupMargin + w) : cx + r + textMargin + popupMargin) + " , " +
|
||||||
(cy - ((y - (h - 5)) / 2)) +
|
(cy - ((y - (h - textMargin)) / 2)) +
|
||||||
")");
|
")");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue