|
import { select } from 'd3-selection' |
|
import { format, formatPrefix } from 'd3-format' |
|
|
|
const d3_identity = (d) => d |
|
|
|
const d3_reverse = (arr) => { |
|
const mirror = []; |
|
for (let i = 0, l = arr.length; i < l; i++) { |
|
mirror[i] = arr[l-i-1]; |
|
} |
|
return mirror; |
|
} |
|
|
|
//Text wrapping code adapted from Mike Bostock |
|
const d3_textWrapping = (text, width) => { |
|
text.each(function() { |
|
var text = select(this), |
|
words = text.text().split(/\s+/).reverse(), |
|
word, |
|
line = [], |
|
lineNumber = 0, |
|
lineHeight = 1.2, //ems |
|
y = text.attr("y"), |
|
dy = parseFloat(text.attr("dy")) || 0, |
|
tspan = text.text(null) |
|
.append("tspan") |
|
.attr("x", 0) |
|
.attr("dy", dy + "em"); |
|
|
|
while (word = words.pop()) { |
|
line.push(word); |
|
tspan.text(line.join(" ")); |
|
if (tspan.node().getComputedTextLength() > width && line.length > 1) { |
|
line.pop(); |
|
tspan.text(line.join(" ")); |
|
line = [word]; |
|
tspan = text.append("tspan") |
|
.attr("x", 0) |
|
.attr("dy", lineHeight + dy + "em").text(word); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
|
|
const d3_mergeLabels = (gen=[], labels, domain, range) => { |
|
|
|
if (typeof labels === "object"){ |
|
if(labels.length === 0) return gen; |
|
|
|
let i = labels.length; |
|
for (; i < gen.length; i++) { |
|
labels.push(gen[i]); |
|
} |
|
return labels; |
|
} else if (typeof labels === "function") { |
|
const customLabels = [] |
|
const genLength = gen.length |
|
for (let i=0; i < genLength; i++){ |
|
customLabels.push(labels({ |
|
i, |
|
genLength, |
|
generatedLabels : gen, |
|
domain, |
|
range })) |
|
} |
|
return customLabels |
|
} |
|
|
|
return gen; |
|
} |
|
|
|
const d3_linearLegend = (scale, cells, labelFormat) => { |
|
let data = []; |
|
|
|
if (cells.length > 1){ |
|
data = cells; |
|
|
|
} else { |
|
const domain = scale.domain(), |
|
increment = (domain[domain.length - 1] - domain[0])/(cells - 1) |
|
let i = 0; |
|
|
|
for (; i < cells; i++){ |
|
data.push(domain[0] + i*increment); |
|
} |
|
} |
|
|
|
const labels = data.map(labelFormat); |
|
return {data: data, |
|
labels: labels, |
|
feature: d => scale(d)}; |
|
} |
|
|
|
const d3_quantLegend = (scale, labelFormat, labelDelimiter) => { |
|
const labels = scale.range().map( d => { |
|
const invert = scale.invertExtent(d); |
|
return labelFormat(invert[0]) + " " + labelDelimiter + " " + labelFormat(invert[1]); |
|
}); |
|
|
|
return {data: scale.range(), |
|
labels: labels, |
|
feature: d3_identity |
|
}; |
|
} |
|
|
|
const d3_ordinalLegend= scale => ({data: scale.domain(), |
|
labels: scale.domain(), |
|
feature: d => scale(d) } |
|
) |
|
|
|
const d3_cellOver = (cellDispatcher, d, obj) => { |
|
cellDispatcher.call("cellover", obj, d); |
|
} |
|
|
|
const d3_cellOut = (cellDispatcher, d, obj) => { |
|
cellDispatcher.call("cellout", obj, d); |
|
} |
|
|
|
const d3_cellClick = (cellDispatcher, d, obj) => { |
|
cellDispatcher.call("cellclick", obj, d); |
|
} |
|
|
|
|
|
export default { |
|
|
|
d3_drawShapes: (shape, shapes, shapeHeight, shapeWidth, shapeRadius, path) => { |
|
if (shape === "rect"){ |
|
shapes.attr("height", shapeHeight) |
|
.attr("width", shapeWidth); |
|
|
|
} else if (shape === "circle") { |
|
shapes.attr("r", shapeRadius) |
|
|
|
} else if (shape === "line") { |
|
shapes.attr("x1", 0).attr("x2", shapeWidth).attr("y1", 0).attr("y2", 0); |
|
|
|
} else if (shape === "path") { |
|
shapes.attr("d", path); |
|
} |
|
}, |
|
|
|
d3_addText: function (svg, enter, labels, classPrefix, labelWidth){ |
|
enter.append("text").attr("class", classPrefix + "label"); |
|
const text = svg.selectAll(`g.${classPrefix}cell text.${classPrefix}label`) |
|
.data(labels) |
|
.text(d3_identity); |
|
|
|
if (labelWidth){ |
|
svg.selectAll(`g.${classPrefix}cell text.${classPrefix}label`) |
|
.call(d3_textWrapping, labelWidth) |
|
} |
|
|
|
return text |
|
}, |
|
|
|
d3_calcType: function (scale, ascending, cells, labels, labelFormat, labelDelimiter){ |
|
const type = scale.invertExtent ? |
|
d3_quantLegend(scale, labelFormat, labelDelimiter) : scale.ticks ? |
|
d3_linearLegend(scale, cells, labelFormat) : d3_ordinalLegend(scale); |
|
|
|
//for d3.scaleSequential that doesn't have a range function |
|
const range = scale.range && scale.range() || scale.domain() |
|
type.labels = d3_mergeLabels(type.labels, labels, scale.domain(), range); |
|
|
|
if (ascending) { |
|
type.labels = d3_reverse(type.labels); |
|
type.data = d3_reverse(type.data); |
|
} |
|
|
|
return type; |
|
}, |
|
|
|
d3_filterCells: (type, cellFilter) => { |
|
let filterCells = type.data.map((d, i) => ({ data: d, label: type.labels[i] })) |
|
.filter(cellFilter) |
|
const dataValues = filterCells.map(d => d.data) |
|
const labelValues = filterCells.map(d => d.label) |
|
type.data = type.data.filter(d => dataValues.indexOf(d) !== -1) |
|
type.labels = type.labels.filter(d => labelValues.indexOf(d) !== -1) |
|
return type |
|
}, |
|
|
|
d3_placement: (orient, cell, cellTrans, text, textTrans, labelAlign) => { |
|
cell.attr("transform", cellTrans); |
|
text.attr("transform", textTrans); |
|
if (orient === "horizontal"){ |
|
text.style("text-anchor", labelAlign); |
|
} |
|
}, |
|
|
|
d3_addEvents: function(cells, dispatcher){ |
|
cells.on("mouseover.legend", function (d) { d3_cellOver(dispatcher, d, this); }) |
|
.on("mouseout.legend", function (d) { d3_cellOut(dispatcher, d, this); }) |
|
.on("click.legend", function (d) { d3_cellClick(dispatcher, d, this); }); |
|
}, |
|
|
|
d3_title: (svg, title, classPrefix, titleWidth) => { |
|
if (title !== ""){ |
|
|
|
const titleText = svg.selectAll('text.' + classPrefix + 'legendTitle'); |
|
|
|
titleText.data([title]) |
|
.enter() |
|
.append('text') |
|
.attr('class', classPrefix + 'legendTitle'); |
|
|
|
svg.selectAll('text.' + classPrefix + 'legendTitle') |
|
.text(title) |
|
|
|
if (titleWidth){ |
|
svg.selectAll('text.' + classPrefix + 'legendTitle') |
|
.call(d3_textWrapping, titleWidth) |
|
} |
|
|
|
const cellsSvg = svg.select('.' + classPrefix + 'legendCells') |
|
const yOffset = svg.select('.' + classPrefix + 'legendTitle').nodes() |
|
.map(d => d.getBBox().height)[0], |
|
|
|
xOffset = -cellsSvg.nodes().map(function(d) { return d.getBBox().x})[0]; |
|
cellsSvg.attr('transform', 'translate(' + xOffset + ',' + (yOffset) + ')'); |
|
|
|
} |
|
}, |
|
|
|
d3_defaultLocale: { |
|
format, |
|
formatPrefix |
|
}, |
|
|
|
d3_defaultFormatSpecifier: '.01f', |
|
|
|
d3_defaultDelimiter: 'to' |
|
} |