@flourish/legend
Advanced tools
Comparing version 1.3.2 to 2.0.0
{ | ||
"name": "@flourish/legend", | ||
"version": "1.3.2", | ||
"version": "2.0.0", | ||
"description": "Flourish module for making legend", | ||
@@ -17,2 +17,3 @@ "main": "legend.js", | ||
"dependencies": { | ||
"d3-scale": "^3.2.1", | ||
"d3-selection": "^1.4.0" | ||
@@ -19,0 +20,0 @@ }, |
@@ -0,1 +1,6 @@ | ||
# 2.0.0 | ||
* Support binned legend labels | ||
* Adjust alignment | ||
* Size legends tweaks | ||
# 1.3.2 | ||
@@ -2,0 +7,0 @@ * Unblur canvas elements |
@@ -44,2 +44,3 @@ function appendTo(container) { | ||
.style("font-size", this._state.text_size + "rem") | ||
.style("line-height", "1em") | ||
.style("margin-top", 0) | ||
@@ -46,0 +47,0 @@ .style("margin-bottom", 0) |
import { select } from "d3-selection"; | ||
import { scaleLinear } from "d3-scale"; | ||
import { appendTo, format, getContainer, visible, _updateTitle } from "../common"; | ||
@@ -17,3 +18,6 @@ import { remToPx } from "../utils"; | ||
text_color: null, | ||
text_size: 1 | ||
text_size: 1, | ||
binned_label_mode: "thresholds", | ||
binned_label_custom: "" | ||
}); | ||
@@ -34,3 +38,5 @@ | ||
this._container.append("div").classed("min label", true); | ||
this._container.append("canvas").classed("color-range", true); | ||
this._container.append("div").classed("color-range-container", true); | ||
this._container.select(".color-range-container").append("canvas").classed("color-range", true); | ||
this._container.select(".color-range-container").append("canvas").classed("color-range-labels", true); | ||
this._container.append("div").classed("max label", true); | ||
@@ -50,3 +56,2 @@ | ||
ContinuousColorLegend.prototype.data = function(items_, colorFunction_) { | ||
@@ -77,56 +82,187 @@ // items_ must be a 2-element domain array [minValue, maxValue] | ||
ContinuousColorLegend.prototype._updateLegend = function() { | ||
this._scale_type = "continuous"; | ||
if (this._colorFunction.midpoint) this._scale_type = "diverging"; | ||
else if (this._colorFunction.thresholds) this._scale_type = "binned"; | ||
var domain = this._legend_domain; | ||
var format = this._formatFunction; | ||
var s = this._state; | ||
var pixel_ratio = window.devicePixelRatio || 1; | ||
var label_padding = this._scale_type == "continuous" ? 0 : remToPx(s.text_size); | ||
var w = remToPx(s.color_band_width), h = remToPx(s.color_band_height), label_h = Math.ceil(remToPx(s.text_size) * 0.95), label_w = w + label_padding * 2; | ||
this._container.style("display", "flex") | ||
.style("align-items", "center"); | ||
this._container.select(".color-range-container") | ||
.style("display", "inline-block"); | ||
var canvas = this._container | ||
.select(".color-range") | ||
.attr("width", w * pixel_ratio) | ||
.attr("height", h * pixel_ratio) | ||
.style("margin", "0 " + label_padding + "px") | ||
.style("border-radius", s.color_band_radius + "px") | ||
.style("width", w + "px") | ||
.style("height", h + "px") | ||
.style("display", "block"); | ||
this._container | ||
.select(".color-range-labels") | ||
.attr("width", label_w * pixel_ratio) | ||
.attr("height", label_h * pixel_ratio) | ||
.style("width", label_w + "px") | ||
.style("height", label_h + "px") | ||
.style("display", this._scale_type == "continuous" ? "none" : "block") | ||
.style("margin-top", s.text_size * 0.25 + "rem") | ||
.node().getContext("2d").scale(pixel_ratio, pixel_ratio); | ||
// Update canvas | ||
var ctx = canvas.node().getContext("2d"); | ||
ctx.scale(pixel_ratio, pixel_ratio); | ||
function xToVal(x) { | ||
return domain[0] + (x / w) * (domain[1] - domain[0]); | ||
} | ||
for (var x = 0; x < w; x++) { | ||
ctx.fillStyle = this._colorFunction(xToVal(x)); | ||
ctx.fillRect(x, 0, 1, h); | ||
} | ||
this._updateLegendLabels(); | ||
}; | ||
ContinuousColorLegend.prototype._updateLegendLabels = function () { | ||
var label_canvas = this._container.select(".color-range-labels"); | ||
var format = this._formatFunction; | ||
var domain = this._legend_domain; | ||
var s = this._state; | ||
this._container | ||
.select(".min.label") | ||
.style("display", "inline-block") | ||
.style("display", this._scale_type == "continuous" ? "inline-block" : "none") | ||
.style("vertical-align", "middle") | ||
.style("user-select", "none") | ||
.style("margin-right", s.text_size * 0.25 + "rem") | ||
.style("margin-right", s.text_size * 0.5 + "rem") | ||
.style("font-size", s.text_size + "rem") | ||
.style("line-height", "1em") | ||
.style("color", s.text_color) | ||
.text(function() { | ||
.text(function () { | ||
return format ? format(domain[0]) : domain[0]; | ||
}); | ||
var w = remToPx(s.color_band_width), h = remToPx(s.color_band_height); | ||
var canvas = this._container | ||
.select(".color-range") | ||
.style("width", w + "px") | ||
.style("height", h + "px") | ||
.attr("width", w * pixel_ratio) | ||
.attr("height", h * pixel_ratio) | ||
.style("vertical-align", "middle") | ||
.style("border-radius", s.color_band_radius + "px"); | ||
this._container | ||
.select(".max.label") | ||
.style("display", "inline-block") | ||
.style("display", this._scale_type == "continuous" ? "inline-block" : "none") | ||
.style("vertical-align", "middle") | ||
.style("user-select", "none") | ||
.style("margin-left", s.text_size * 0.25 + "rem") | ||
.style("margin-left", s.text_size * 0.5 + "rem") | ||
.style("font-size", s.text_size + "rem") | ||
.style("line-height", "1em") | ||
.style("color", s.text_color) | ||
.text(function() { | ||
.text(function () { | ||
return format ? format(domain[1]) : domain[1]; | ||
}); | ||
var labels = []; | ||
if (this._scale_type === "diverging") labels = this._getDivergingLabels(); | ||
else if (this._scale_type === "binned") labels = this._getBinnedLabels(); | ||
// Update canvas | ||
var ctx = canvas.node().getContext("2d"); | ||
ctx.scale(pixel_ratio, pixel_ratio); | ||
if (labels.length) { | ||
var template = window.template || undefined; | ||
var layout = template ? template.state.layout : undefined; | ||
var font_family = layout ? layout.body_font.name : "sans-serif"; | ||
var font_color = s.text_color || (template && layout.font_color) || "#333333"; | ||
var ctx = label_canvas.node().getContext("2d"); | ||
ctx.textAlign = "center"; | ||
ctx.textBaseline = "top"; | ||
ctx.fillStyle = font_color; | ||
ctx.font = remToPx(s.text_size) + "px " + font_family; | ||
function xToVal(x) { | ||
return domain[0] + (x / w) * (domain[1] - domain[0]); | ||
var prepLabel = (function () { | ||
var placed_labels = []; | ||
var label_padding = remToPx(s.text_size); | ||
var inRange = getInRangeFunction([0, label_canvas.node().width]); | ||
return function (value) { | ||
var scale = scaleLinear().domain(domain).range([label_padding, remToPx(s.color_band_width) + label_padding]); | ||
var center = scale(value); | ||
if (!inRange(center)) return; | ||
var text = format ? format(value) : value; | ||
var half_width = ctx.measureText(text).width / 2 + 1; | ||
var left = center - half_width; | ||
var right = center + half_width; | ||
if (!inRange(left) || !inRange(right)) return; | ||
var index = 0; | ||
var n = placed_labels.length; | ||
for (var i = 0; i < n; i++) { | ||
if (center > placed_labels[i].center) index++; | ||
else break; | ||
} | ||
var at_start = index === 0; | ||
var at_end = index === n; | ||
if (!at_start && placed_labels[index - 1].right > left) return; | ||
if (!at_end && placed_labels[index].left < right) return; | ||
placed_labels.splice(index, 0, { left: left, center: center, right: right }); | ||
return { text: text, position: center }; | ||
}; | ||
})(); | ||
labels.forEach(function (value) { | ||
var label = prepLabel(value); | ||
if (label) ctx.fillText(label.text, label.position, 0); | ||
}); | ||
} | ||
}; | ||
for (var x = 0; x < w; x++) { | ||
ctx.fillStyle = this._colorFunction(xToVal(x)); | ||
ctx.fillRect(x, 0, 1, h); | ||
ContinuousColorLegend.prototype._getDivergingLabels = function() { | ||
var legend_domain = this._legend_domain; | ||
var inRange = getInRangeFunction(legend_domain); | ||
var labels = []; | ||
if (inRange(this._colorFunction.midpoint)) labels.push(this._colorFunction.midpoint); | ||
var gradient_domain = this._colorFunction.domain; | ||
if (inRange(gradient_domain[0])) labels.push(gradient_domain[0]); | ||
if (inRange(gradient_domain[1])) labels.push(gradient_domain[1]); | ||
if (legend_domain[0] !== gradient_domain[0]) labels.push(legend_domain[0]); | ||
if (legend_domain[1] !== gradient_domain[1]) labels.push(legend_domain[1]); | ||
return labels; | ||
}; | ||
ContinuousColorLegend.prototype._getBinnedLabels = function () { | ||
var s = this._state; | ||
var legend_domain = this._legend_domain; | ||
var inRange = getInRangeFunction(legend_domain); | ||
var labels; | ||
if (s.binned_label_mode === "thresholds") { | ||
labels = this._colorFunction.thresholds.slice().filter(inRange); | ||
if (legend_domain[0] < labels[0]) labels.push(legend_domain[0]); | ||
if (legend_domain[1] > labels[labels.length - 1]) labels.push(legend_domain[1]); | ||
} | ||
else if (s.binned_label_mode === "centers") { | ||
labels = this._colorFunction.centers.slice().filter(inRange); | ||
if (labels.length) { | ||
var thresholds = this._colorFunction.thresholds; | ||
var first_threshold = thresholds[0]; | ||
var last_threshold = thresholds[thresholds.length - 1]; | ||
if (first_threshold > legend_domain[0]) labels.push((legend_domain[0] + first_threshold) / 2); | ||
if (last_threshold < legend_domain[1]) labels.push((last_threshold + legend_domain[1]) / 2); | ||
} | ||
else labels.push((legend_domain[0] + legend_domain[1]) / 2); | ||
} | ||
else labels = s.binned_label_custom.split(";").map(parseFloat); | ||
return labels; | ||
}; | ||
function getInRangeFunction(legend_domain) { | ||
return function (val) { return val >= legend_domain[0] && val <= legend_domain[1]; }; | ||
} | ||
export default ContinuousColorLegend; |
@@ -13,2 +13,3 @@ import { select } from "d3-selection"; | ||
clip_height: 1, | ||
small_circle_size: 0.5, | ||
@@ -82,3 +83,3 @@ text_color: null, | ||
.style("align-items", "center") | ||
.style("height", h + "px"); | ||
.style("min-height", h + "px"); | ||
@@ -103,5 +104,7 @@ if (!should_display || !this._scaleFunction) return this; | ||
var domain = this._scaleFunction.domain(); | ||
var range = this._scaleFunction.range(); | ||
var format = this._formatFunction; | ||
var s = this._state; | ||
var h = Math.ceil(remToPx(s.clip_height)); | ||
var h = remToPx(s.clip_height); | ||
h = Math.ceil(Math.min(h, range[1] * 2)); | ||
var pixel_ratio = window.devicePixelRatio || 1; | ||
@@ -112,4 +115,10 @@ | ||
// Calculate max circle size | ||
var val_max_unrounded = this._scaleFunction.invert(h/2); | ||
val_max_unrounded = Math.min(val_max_unrounded, domain[1]); | ||
var val_max = roundDown(val_max_unrounded) || 0; | ||
// Min circle | ||
var r_min = 1; | ||
var val_min = val_max * s.small_circle_size; | ||
var r_min = this._scaleFunction(val_min); | ||
var w_min = Math.ceil(2 * r_min) + 2; | ||
@@ -122,3 +131,3 @@ var canvas_min = this._container | ||
.attr("height", h * pixel_ratio) | ||
.style("margin-right", "0.1rem"); | ||
.style("margin-right", "0.05rem"); | ||
@@ -133,3 +142,3 @@ this._container | ||
.text(function () { | ||
return format ? format(domain[0]) : domain[0]; | ||
return format ? format(val_min) : val_min; | ||
}); | ||
@@ -140,15 +149,15 @@ | ||
ctx_min.fillStyle = fill; | ||
ctx_min.strokeStyle = stroke; | ||
ctx_min.clearRect(0, 0, 2 * r_min, 2 * r_min); | ||
ctx_min.beginPath(); | ||
ctx_min.fillStyle = stroke; | ||
ctx_min.arc(0.5 * w_min, 0.5 * h, r_min, 0, 2 * Math.PI); | ||
ctx_min.fill(); | ||
ctx_min.stroke(); | ||
ctx_min.beginPath(); | ||
ctx_min.fillStyle = fill; | ||
ctx_min.arc(0.5 * w_min, 0.5 * h, Math.max(r_min - 1, 0), 0, 2 * Math.PI); | ||
ctx_min.fill(); | ||
// Max circle | ||
var val_max_unrounded = this._scaleFunction.invert(h / 2); | ||
val_max_unrounded = Math.min(val_max_unrounded, domain[1]); | ||
var val_max = roundDown(val_max_unrounded); | ||
var r_max = this._scaleFunction(val_max); | ||
@@ -163,3 +172,3 @@ var w_max = Math.ceil(2 * r_max) + 2; | ||
.attr("height", h * pixel_ratio) | ||
.style("margin-left", "0.1rem"); | ||
.style("margin-left", "0.05rem"); | ||
@@ -191,3 +200,3 @@ this._container | ||
ctx_max.fillStyle = fill; | ||
ctx_max.arc(0.5 * w_max, 0.5 * h, r_max - 0.5, 0, 2 * Math.PI); | ||
ctx_max.arc(0.5 * w_max, 0.5 * h, Math.max(r_max - 1, 0), 0, 2 * Math.PI); | ||
ctx_max.fill(); | ||
@@ -194,0 +203,0 @@ }; |
@@ -133,2 +133,5 @@ import { select, event as d3_event } from "d3-selection"; | ||
var _this = this; | ||
this._container.style("line-height", "0") | ||
.style("display", "inline-flex") | ||
.style("align-items", "center"); | ||
@@ -153,2 +156,4 @@ var legend_item_data; | ||
}) | ||
.style("line-height", "0") | ||
.style("vertical-align", "top") | ||
.style("margin-right", this._state.orientation == "horizontal" ? this._state.text_size * 0.5 + "rem" : ""); | ||
@@ -167,2 +172,3 @@ | ||
.style("font-size", this._state.text_size + "rem") | ||
.style("line-height", "1em") | ||
.style("color", this._state.text_color) | ||
@@ -169,0 +175,0 @@ .text(function(d) { return d.label; }) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
155861
3970
2
+ Addedd3-scale@^3.2.1
+ Addedd3-array@2.12.1(transitive)
+ Addedd3-color@2.0.0(transitive)
+ Addedd3-format@2.0.0(transitive)
+ Addedd3-interpolate@2.0.1(transitive)
+ Addedd3-scale@3.3.0(transitive)
+ Addedd3-time@2.1.1(transitive)
+ Addedd3-time-format@3.0.0(transitive)
+ Addedinternmap@1.0.1(transitive)