d3-array#max JavaScript Examples
The following examples show how to use
d3-array#max.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: render.js From the-eye-knows-the-garbage with MIT License | 6 votes |
// Creates a hexjson grid with the layout and dimensions of the given hexjson
export function getGridForHexJSON (hexjson) {
// Create a new HexJSON object for the grid
var grid = {};
grid.layout = hexjson.layout;
grid.hexes = {};
// Get the hex objects from the hexjson as an array
var hexes = [];
Object.keys(hexjson.hexes).forEach(function (key) {
hexes.push(hexjson.hexes[key]);
});
// Calculate the number of rows and columns in the grid
var qmax = max(hexes, function (d) { return +d.q }),
qmin = min(hexes, function (d) { return +d.q }),
rmax = max(hexes, function (d) { return +d.r }),
rmin = min(hexes, function (d) { return +d.r });
// Create the hexjson grid
var i, j, fkey;
for (i = qmin; i <= qmax; i++) {
for (j = rmin; j <= rmax; j++) {
fkey = "Q" + i + "R" + j;
grid.hexes[fkey] = {q: i, r: j};
}
}
return grid;
}
Example #2
Source File: horizontal-bar-chart.js From website with Apache License 2.0 | 5 votes |
HorizontalBarChart = ({
data,
fill,
height,
marginBottom = 0,
marginLeft = 0,
marginRight = 0,
marginTop = 0,
xTicks,
width,
xMax = null,
}) => {
const totalXMargin = marginLeft + marginRight
const totalYMargin = marginTop + marginBottom
const yScale = scaleBand()
.domain(data.map(d => d.name))
.range([0, height - totalYMargin])
.padding(0.2)
const formatTick = format('~s')
const xScale = scaleLinear()
.domain([120, xMax || max(data, d => d.value)])
.nice()
.range([0, width - totalXMargin])
return (
<svg
className={chartStyles.chart}
viewBox={`0 0 ${width} ${height}`}
aria-hidden
>
<g transform={`translate(${marginLeft} ${marginTop})`}>
{xScale.ticks(xTicks).map(tick => (
<g key={tick}>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
x={xScale(tick)}
y={height - marginBottom}
>
{formatTick(tick)}
</text>
<line
className={chartStyles.gridLine}
x1={xScale(tick)}
x2={xScale(tick)}
y1={0}
y2={height - totalYMargin}
/>
</g>
))}
</g>
<g transform={`translate(0, ${marginTop})`}>
{data.map(d => (
/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */
<svg
y={yScale(d.name) + 20}
x={marginLeft - 10}
className={chartStyles.yTickLabel}
key={d.name}
>
<text className={chartStyles.label}>{`${d.name}`}</text>
</svg>
))}
</g>
<g transform={`translate(${marginLeft}, ${marginTop})`}>
{data.map(d => (
<rect
key={d.name}
x={0}
y={yScale(d.name)}
height={yScale.bandwidth()}
width={xScale(d.value)}
fill={fill}
/>
))}
</g>
</svg>
)
}
Example #3
Source File: render.js From the-eye-knows-the-garbage with MIT License | 5 votes |
// Creates a list of dots along the boundaries between
// hexes which have different values of "field"
export function getBoundaryDotsForHexJSON (hexjson, width, height, field) {
// Get the hex objects from the hexjson as an array
var hexes = [];
const layout = hexjson.layout;
Object.keys(hexjson.hexes).forEach(function (key) {
hexes.push(hexjson.hexes[key]);
});
// Calculate the number of rows and columns
var qmax = max(hexes, function (d) { return +d.q }),
qmin = min(hexes, function (d) { return +d.q }),
rmax = max(hexes, function (d) { return +d.r }),
rmin = min(hexes, function (d) { return +d.r });
var qnum = qmax - qmin + 1,
rnum = rmax - rmin + 1;
var hexRadius;
// Calculate maximum radius the hexagons can have to fit the svg
if (layout === "odd-r" || layout === "even-r") {
hexRadius = min([(width) / ((qnum + 0.5) * Math.sqrt(3)),
height / ((rnum + 1 / 3) * 1.5)]);
} else {
hexRadius = min([(height) / ((rnum + 0.5) * Math.sqrt(3)),
width / ((qnum + 1 / 3) * 1.5)]);
}
// Calculate the hexagon width
var hexWidth = hexRadius * Math.sqrt(3);
// Create an array into which we will put points along the
// boundaries between differing hexes.
// Each edge has five points, equally spaced.
var lines = [];
const hexRadiusSquared = hexRadius * hexRadius * 4;
const maxHex = hexes.length;
if (maxHex > 1) {
hexes.forEach(function (hex) {
hex.qc = hex.q - qmin;
hex.rc = rmax - hex.r;
// Calculate the x and y position of each hex for this svg
hex.x = getX(hex, layout, hexWidth, hexRadius);
hex.y = getY(hex, layout, hexWidth, hexRadius);
});
for (var i = 0; i < maxHex - 1; i++) {
for (var j = i + 1; j < maxHex; j++) {
var hex = hexes[i];
var otherHex = hexes[j];
if (hex[field] !== otherHex[field]) {
if (Math.abs(hex.q - otherHex.q) <= 1 &&
Math.abs(hex.r - otherHex.r) <= 1) {
if (((hex.x - otherHex.x) * (hex.x - otherHex.x)) +
((hex.y - otherHex.y) * (hex.y - otherHex.y)) < hexRadiusSquared) {
// They're neighbours
var midpoint = {};
midpoint.x = otherHex.x + (hex.x - otherHex.x) / 2;
midpoint.y = otherHex.y + (hex.y - otherHex.y) / 2;
var perp = {};
const denom = Math.sqrt(3) * 4;
perp.dx = (hex.y - otherHex.y) / denom;
perp.dy = -(hex.x - otherHex.x) / denom;
lines.push({x: midpoint.x - 2 * perp.dx, y: midpoint.y - 2 * perp.dy});
lines.push({x: midpoint.x - perp.dx, y: midpoint.y - perp.dy});
lines.push({x: midpoint.x, y: midpoint.y});
lines.push({x: midpoint.x + perp.dx, y: midpoint.y + perp.dy});
lines.push({x: midpoint.x + 2 * perp.dx, y: midpoint.y + 2 * perp.dy});
}
}
}
}
}
}
return lines;
}
Example #4
Source File: sparklines.js From website with Apache License 2.0 | 5 votes |
StatisticSparkline = ({ data, field, color }) => {
const height = 40
const width = 120
const marginTop = 5
const marginRight = 20
const dates = []
const values = []
const averages = []
data.forEach((item, key) => {
if (key < 14) {
return
}
let sum = 0
for (let i = key; i > key - 7; i -= 1) {
sum += data[i][field]
}
averages.push({
value: sum / 7,
date: DateTime.fromISO(item.date).toJSDate(),
})
})
averages.forEach(item => {
dates.push(item.date)
values.push(item.value)
})
const dateDomain = extent(dates)
const xScaleTime = scaleTime()
.domain(dateDomain)
.range([0, width])
const yMaxEffective = max(values)
const yScale = scaleLinear()
.domain([0, yMaxEffective])
.nice()
.range([height, 0])
const lineFn = line()
.defined(d => !Number.isNaN(d.value) && d.value !== null)
.curve(curveNatural)
.x(d => xScaleTime(d.date))
.y(d => yScale(d.value))
return (
<svg
className={sparklineStyles.sparkline}
viewBox={`0 0 ${width + marginRight} ${height - marginTop}`}
aria-hidden
>
<g transform={`translate(0 ${marginTop})`}>
<defs>
<marker
id={`triangle-${field}`}
refX="10"
refY="6"
markerWidth="30"
markerHeight="30"
markerUnits="userSpaceOnUse"
orient="auto"
>
<path d="M 0 0 12 6 0 12 3 6" style={{ fill: color }} />
</marker>
</defs>
<path
d={lineFn(averages)}
stroke={color}
strokeWidth={3}
fill="none"
markerEnd={`url(#triangle-${field})`}
/>
</g>
</svg>
)
}
Example #5
Source File: render.js From the-eye-knows-the-garbage with MIT License | 5 votes |
// Main render method
export function renderHexJSON (hexjson, width, height) {
// Get the layout
var layout = hexjson.layout;
// Get the hex objects as an array
var hexes = [];
var hexRadius = 0;
Object.keys(hexjson.hexes).forEach(function (key) {
hexjson.hexes[key].key = key;
hexes.push(hexjson.hexes[key]);
});
// Calculate the number of rows and columns
var qmax = max(hexes, function (d) { return +d.q }),
qmin = min(hexes, function (d) { return +d.q }),
rmax = max(hexes, function (d) { return +d.r }),
rmin = min(hexes, function (d) { return +d.r });
var qnum = qmax - qmin + 1,
rnum = rmax - rmin + 1;
// Calculate maximum radius the hexagons can have to fit the svg
if (layout === "odd-r" || layout === "even-r") {
hexRadius = min([(width) / ((qnum + 0.5) * Math.sqrt(3)),
height / ((rnum + 1 / 3) * 1.5)]);
} else {
hexRadius = min([(height) / ((rnum + 0.5) * Math.sqrt(3)),
width / ((qnum + 1 / 3) * 1.5)]);
}
// Calculate the hexagon width
var hexWidth = hexRadius * Math.sqrt(3);
// Get the vertices and points for this layout
var vertices = getVertices(layout, hexWidth, hexRadius);
var points = getPoints(vertices);
// Calculate the values needed to render each hex and add to hexes
hexes.forEach(function (hex) {
// Calculate the absolute co-ordinates of each hex
hex.qc = hex.q - qmin;
hex.rc = rmax - hex.r;
// Calculate the x and y position of each hex for this svg
hex.x = getX(hex, layout, hexWidth, hexRadius);
hex.y = getY(hex, layout, hexWidth, hexRadius);
// Add the vertex positions and points relative to x and y
hex.vertices = vertices;
hex.points = points;
});
return hexes;
}
Example #6
Source File: size.js From t-viSNE with MIT License | 4 votes |
export default function size() {
let scale = scaleLinear(),
shape = "rect",
shapeWidth = 15,
shapePadding = 2,
cells = [5],
cellFilter,
labels = [],
classPrefix = "",
title = "",
locale = helper.d3_defaultLocale,
specifier = helper.d3_defaultFormatSpecifier,
labelOffset = 10,
labelAlign = "middle",
labelDelimiter = helper.d3_defaultDelimiter,
labelWrap,
orient = "vertical",
ascending = false,
path,
titleWidth,
legendDispatcher = dispatch("cellover", "cellout", "cellclick")
function legend(svg) {
const type = helper.d3_calcType(
scale,
ascending,
cells,
labels,
locale.format(specifier),
labelDelimiter
),
legendG = svg.selectAll("g").data([scale])
if (cellFilter) {
helper.d3_filterCells(type, cellFilter)
}
legendG
.enter()
.append("g")
.attr("class", classPrefix + "legendCells")
let cell = svg
.select("." + classPrefix + "legendCells")
.selectAll("." + classPrefix + "cell")
.data(type.data)
const cellEnter = cell
.enter()
.append("g")
.attr("class", classPrefix + "cell")
cellEnter.append(shape).attr("class", classPrefix + "swatch")
let shapes = svg.selectAll(
"g." + classPrefix + "cell " + shape + "." + classPrefix + "swatch"
)
//add event handlers
helper.d3_addEvents(cellEnter, legendDispatcher)
cell
.exit()
.transition()
.style("opacity", 0)
.remove()
shapes
.exit()
.transition()
.style("opacity", 0)
.remove()
shapes = shapes.merge(shapes)
//creates shape
if (shape === "line") {
helper.d3_drawShapes(shape, shapes, 0, shapeWidth)
shapes.attr("stroke-width", type.feature)
} else {
helper.d3_drawShapes(
shape,
shapes,
type.feature,
type.feature,
type.feature,
path
)
}
const text = helper.d3_addText(
svg,
cellEnter,
type.labels,
classPrefix,
labelWrap
)
// we need to merge the selection, otherwise changes in the legend (e.g. change of orientation) are applied only to the new cells and not the existing ones.
cell = cellEnter.merge(cell)
//sets placement
const textSize = text.nodes().map(d => d.getBBox()),
shapeSize = shapes.nodes().map((d, i) => {
const bbox = d.getBBox()
const stroke = scale(type.data[i])
if (shape === "line" && orient === "horizontal") {
bbox.height = bbox.height + stroke
} else if (shape === "line" && orient === "vertical") {
bbox.width = bbox.width
}
return bbox
})
const maxH = max(shapeSize, d => d.height + d.y),
maxW = max(shapeSize, d => d.width + d.x)
let cellTrans,
textTrans,
textAlign = labelAlign == "start" ? 0 : labelAlign == "middle" ? 0.5 : 1
//positions cells and text
if (orient === "vertical") {
const cellSize = textSize.map((d, i) =>
Math.max(d.height, shapeSize[i].height)
)
const y =
shape == "circle" || shape == "line" ? shapeSize[0].height / 2 : 0
cellTrans = (d, i) => {
const height = sum(cellSize.slice(0, i))
return `translate(0, ${y + height + i * shapePadding})`
}
textTrans = (d, i) => `translate( ${maxW + labelOffset},
${shapeSize[i].y + shapeSize[i].height / 2 + 5})`
} else if (orient === "horizontal") {
cellTrans = (d, i) => {
const width = sum(shapeSize.slice(0, i), d => d.width)
const y = shape == "circle" || shape == "line" ? maxH / 2 : 0
return `translate(${width + i * shapePadding}, ${y})`
}
const offset = shape == "line" ? maxH / 2 : maxH
textTrans = (d, i) => {
return `translate( ${shapeSize[i].width * textAlign + shapeSize[i].x},
${offset + labelOffset})`
}
}
helper.d3_placement(orient, cell, cellTrans, text, textTrans, labelAlign)
helper.d3_title(svg, title, classPrefix, titleWidth)
cell.transition().style("opacity", 1)
}
legend.scale = function(_) {
if (!arguments.length) return scale
scale = _
return legend
}
legend.cells = function(_) {
if (!arguments.length) return cells
if (_.length > 1 || _ >= 2) {
cells = _
}
return legend
}
legend.cellFilter = function(_) {
if (!arguments.length) return cellFilter
cellFilter = _
return legend
}
legend.shape = function(_, d) {
if (!arguments.length) return shape
if (_ == "rect" || _ == "circle" || _ == "line") {
shape = _
path = d
}
return legend
}
legend.shapeWidth = function(_) {
if (!arguments.length) return shapeWidth
shapeWidth = +_
return legend
}
legend.shapePadding = function(_) {
if (!arguments.length) return shapePadding
shapePadding = +_
return legend
}
legend.labels = function(_) {
if (!arguments.length) return labels
labels = _
return legend
}
legend.labelAlign = function(_) {
if (!arguments.length) return labelAlign
if (_ == "start" || _ == "end" || _ == "middle") {
labelAlign = _
}
return legend
}
legend.locale = function(_) {
if (!arguments.length) return locale
locale = formatLocale(_)
return legend
}
legend.labelFormat = function(_) {
if (!arguments.length) return legend.locale().format(specifier)
specifier = formatSpecifier(_)
return legend
}
legend.labelOffset = function(_) {
if (!arguments.length) return labelOffset
labelOffset = +_
return legend
}
legend.labelDelimiter = function(_) {
if (!arguments.length) return labelDelimiter
labelDelimiter = _
return legend
}
legend.labelWrap = function(_) {
if (!arguments.length) return labelWrap
labelWrap = _
return legend
}
legend.orient = function(_) {
if (!arguments.length) return orient
_ = _.toLowerCase()
if (_ == "horizontal" || _ == "vertical") {
orient = _
}
return legend
}
legend.ascending = function(_) {
if (!arguments.length) return ascending
ascending = !!_
return legend
}
legend.classPrefix = function(_) {
if (!arguments.length) return classPrefix
classPrefix = _
return legend
}
legend.title = function(_) {
if (!arguments.length) return title
title = _
return legend
}
legend.titleWidth = function(_) {
if (!arguments.length) return titleWidth
titleWidth = _
return legend
}
legend.on = function() {
const value = legendDispatcher.on.apply(legendDispatcher, arguments)
return value === legendDispatcher ? legend : value
}
return legend
}
Example #7
Source File: render.js From the-eye-knows-the-garbage with MIT License | 4 votes |
// Creates a list of line segments along the boundaries
// between hexes which have different values of "field"
export function getBoundarySegmentsForHexJSON (hexjson, width, height, field) {
// Get the hex objects from the hexjson as an array
var hexes = [];
const layout = hexjson.layout;
Object.keys(hexjson.hexes).forEach(function (key) {
hexes.push(hexjson.hexes[key]);
});
// Calculate the number of rows and columns
var qmax = max(hexes, function (d) { return +d.q }),
qmin = min(hexes, function (d) { return +d.q }),
rmax = max(hexes, function (d) { return +d.r }),
rmin = min(hexes, function (d) { return +d.r });
var qnum = qmax - qmin + 1,
rnum = rmax - rmin + 1;
var hexRadius;
// Calculate maximum radius the hexagons can have to fit the svg
if (layout === "odd-r" || layout === "even-r") {
hexRadius = min([(width) / ((qnum + 0.5) * Math.sqrt(3)),
height / ((rnum + 1 / 3) * 1.5)]);
} else {
hexRadius = min([(height) / ((rnum + 0.5) * Math.sqrt(3)),
width / ((qnum + 1 / 3) * 1.5)]);
}
// Calculate the hexagon width
var hexWidth = hexRadius * Math.sqrt(3);
// Create an array into which we will put points along the
// boundaries between differing hexes.
// Each segment will be of the form
// {x: <start point X>, y: <start point Y>, cx: <difference X>, cy: <difference Y> }
// intended to be used with the simple line drawing functionality of d3
//
var segments = [];
const hexRadiusSquared = hexRadius * hexRadius * 4;
const maxHex = hexes.length;
if (maxHex > 1) {
hexes.forEach(function (hex) {
hex.qc = hex.q - qmin;
hex.rc = rmax - hex.r;
// Calculate the x and y position of each hex for this svg
hex.x = getX(hex, layout, hexWidth, hexRadius);
hex.y = getY(hex, layout, hexWidth, hexRadius);
});
for (var i = 0; i < maxHex - 1; i++) {
for (var j = i + 1; j < maxHex; j++) {
var hex = hexes[i];
var otherHex = hexes[j];
if (hex[field] !== otherHex[field]) {
if (Math.abs(hex.q - otherHex.q) <= 1 &&
Math.abs(hex.r - otherHex.r) <= 1) {
if (((hex.x - otherHex.x) * (hex.x - otherHex.x)) +
((hex.y - otherHex.y) * (hex.y - otherHex.y)) < hexRadiusSquared) {
// They're neighbours
var midpoint = {};
midpoint.x = otherHex.x + (hex.x - otherHex.x) / 2;
midpoint.y = otherHex.y + (hex.y - otherHex.y) / 2;
var perp = {};
var direction = +1;
if (hex[field] < otherHex[field]) {
direction = -1;
} // otherwise, direction will be +1
const denom = Math.sqrt(3) * 2 * direction;
perp.dx = (hex.y - otherHex.y) / denom;
perp.dy = -(hex.x - otherHex.x) / denom;
segments.push({
x1: midpoint.x - perp.dx,
y1: midpoint.y - perp.dy,
x2: midpoint.x + perp.dx,
y2: midpoint.y + perp.dy});
}
}
}
}
}
}
return segments;
}
Example #8
Source File: CurvaturePlot.js From likelihood with MIT License | 4 votes |
CurvatureChart = props => {
const vizRef = useRef(null);
// Stuff
const margin = { top: 20, right: 20, bottom: 30, left: 50 };
const durationTime = 200;
const w = props.width - margin.left - margin.right;
const h = props.width * 0.5 - margin.top - margin.bottom;
const deriv = props.deriv;
const llThetaMLE = props.llThetaMLE;
const llThetaNull = props.llThetaNull;
const test = props.test;
const n = props.n;
const muNull = props.muNull;
// Axes min and max
var xMax, xMin, llTheta;
const sigmaTheta = Math.sqrt(props.sigma2Theta);
xMax = props.muTheta + sigmaTheta * 5;
xMin = props.muTheta - sigmaTheta * 5;
llTheta = 0;
const data1 = useMemo(
() =>
genEstLogLikCurve(
10,
props.muHat,
props.sigma2Hat,
props.muTheta,
props.sigma2Theta
),
[props.width, props.sigma2Hat, props.muHat]
);
const data2 = useMemo(
() =>
genEstLogLikCurve(
n,
props.muHat,
props.sigma2Hat,
props.muTheta,
props.sigma2Theta
),
[n, props.width, props.sigma2Hat, props.muHat]
);
const yMin = min(data1.y.filter(y => isFinite(y)));
const yMax = max(data1.y);
//const yMax = 0.05;
// Create scales
const yScale = scaleLinear()
.domain([yMin, yMax])
.range([h, 0]);
const xScale = scaleLinear()
.domain([xMin, xMax])
.range([0, w]);
// Scales and Axis
const xAxis = axisBottom(xScale);
// Line function
const linex = line()
.x(d => xScale(d[0]))
.y(d => yScale(d[1]));
// Update
useEffect(() => {
createChart(durationTime);
}, [n, props.width]);
const createChart = () => {
const node = vizRef.current;
const gOuter = select(node).attr(
"transform",
"translate(" + margin.left + "," + margin.top + ")"
);
// x Axis
gOuter
.selectAll("g.xAxis")
.data([0])
.enter()
.append("g")
.attr("class", "xAxis");
select(node)
.select("g.xAxis")
.attr("transform", "translate(" + 0 + "," + h + ")")
.call(xAxis);
// x label
gOuter
.selectAll("#x-label")
.data([0])
.enter()
.append("text")
.style("text-anchor", "middle")
.attr("class", "x-label");
select(node)
.selectAll(".x-label")
.attr(
"transform",
"translate(" + w / 2 + " ," + (h + margin.bottom) + ")"
)
.text("μ");
// y label
gOuter
.selectAll("#y-label")
.data([0])
.enter()
.append("text")
.style("text-anchor", "middle")
.attr("id", "y-label");
select(node)
.selectAll("#y-label")
.attr("transform", "rotate(-90)")
.attr("text-anchor", "middle")
.attr("x", -(h / 2))
.attr("y", -40)
.text("Log-Likelihood");
};
const delta = xMax - xMin;
return (
<svg width={props.width} height={props.width * 0.5}>
<g id="outer" ref={vizRef}>
<g className="viz" clipPath="url(#clip)">
<path d={linex(data1.data)} id="logLikReferenceCurve" />
<path d={linex(data2.data)} id="logLikNCurve" />
<line
className={clsx("LRT", test == "LRT" && "highlight")}
x1={xScale(xMin)}
x2={xScale(xMax)}
y1={yScale(llThetaMLE)}
y2={yScale(llThetaMLE)}
/>
<line
className={clsx("LRT", test == "LRT" && "highlight")}
x1={xScale(xMin)}
x2={xScale(xMax)}
y1={yScale(llThetaNull)}
y2={yScale(llThetaNull)}
/>
<line
className={clsx("wald", test == "wald" && "highlight")}
x1={xScale(props.muHat)}
x2={xScale(props.muHat)}
y1={yScale(yMin)}
y2={yScale(yMax)}
/>
<circle
cx={xScale(muNull)}
cy={yScale(llThetaNull)}
r="5"
fill="red"
className="testPointMuNull"
/>
<circle
cx={xScale(props.muHat)}
cy={yScale(llTheta)}
r="5"
className="testPointMu"
/>
</g>
<line
className={clsx("wald", test == "wald" && "highlight")}
x1={xScale(props.muNull)}
x2={xScale(props.muNull)}
y1={yScale(yMin)}
y2={yScale(yMax)}
/>
<line
className={clsx("score", test == "score" && "highlight")}
x1={xScale(props.muNull - delta)}
x2={xScale(props.muNull + delta)}
y1={yScale(llThetaNull - delta * deriv)}
y2={yScale(llThetaNull + delta * deriv)}
/>
</g>
<defs>
<clipPath id="clip">
<rect id="clip-rect" x="0" y="-10" width={w} height={h + 10} />
</clipPath>
</defs>
</svg>
);
}
Example #9
Source File: ContourLogLik.js From likelihood with MIT License | 4 votes |
ContourChart = props => {
const vizRef = useRef(null);
const dispatch = useContext(VizDispatch);
// Stuff
const margin = { top: 0, right: 20, bottom: 40, left: 50 };
const w = props.width - margin.left - margin.right;
const h = props.width * 0.75 - margin.top - margin.bottom;
const sample = props.sample;
const sigmaTheta = Math.sqrt(props.sigma2Theta);
const muMax = props.muTheta + sigmaTheta * 5;
const muMin = props.muTheta - sigmaTheta * 5;
const sigma2MLE = props.sigma2Theta;
const sigma2Max = 1500;
const sigma2Min = 1;
// For gradient ascent illustration
const [spring, set] = useSpring(() => ({
xy: [props.mu, props.sigma2],
immediate: false,
config: { duration: 500 }
}));
const bind = useDrag(({ movement: [mx, my], first, memo }) => {
const muStart = first ? props.mu : memo[0];
const sigma2Start = first ? props.sigma2 : memo[1];
const mu = xScale.invert(xScale(muStart) + mx);
const sigma2 = yScale.invert(yScale(sigma2Start) + my);
dispatch({
name: "contourDrag",
value: { mu: mu, sigma2: sigma2 }
});
return [muStart, sigma2Start];
});
const iterate = () => {
dispatch({
name: "algoIterate",
value: {
increment: 1,
}
});
};
useInterval(() => {
iterate();
}, props.algoDelay);
set({ xy: [props.mu, props.sigma2], immediate: !props.animating });
const llMin = -300;
const llMax = -20;
const thresholds = useMemo(
() => range(llMin, llMax, (llMax - llMin) / 100),
[]
);
const yScale = scaleLinear([sigma2Min, sigma2Max], [h, 0]);
const xScale = scaleLinear([muMin, muMax], [0, w]);
const linex = useMemo(
() =>
line()
.x(d => xScale(d.mu))
.y(d => yScale(d.sigma2)),
[w]
);
const grid = useMemo(
() => createGrid(muMin, muMax, sigma2Min, sigma2Max, sample),
[props.sample]
);
const color = useMemo(
() =>
scaleLinear()
.domain([-100, max(grid)])
.range(["#82b3aa", "#fff"])
.interpolate(interpolateRgb.gamma(0.6)),
[props.sample]
);
const contour = useMemo(
() =>
contours()
.size([grid.n, grid.m])
.thresholds(thresholds)(grid)
.map(({ type, value, coordinates }) => {
return {
type,
value,
coordinates: coordinates.map(rings => {
return rings.map(points => {
return points.map(([mu, sigma2]) => [
xScale(muMin + mu * grid.muStep),
yScale(sigma2Min + sigma2 * grid.sigmaStep)
]);
});
})
};
}),
[props.sample, w]
);
const contourPaths = useMemo(
() =>
contour.map((d, i) => {
return (
<path
d={geoPath()(d)}
className="contour"
fill={color(d.value)}
fillOpacity={1}
stroke="#485460"
strokeWidth={i % 5 ? 0.5 : 1.5}
strokeOpacity={0.75}
strokeLinejoin="round"
key={i}
/>
);
}),
[props.sample, w]
);
const ll = useMemo(
() => format(".2f")(logLikSum(sample, props.mu, props.sigma2)),
[sample, props.mu, props.sigma2]
);
return (
<svg width={props.width} height={h + margin.bottom}>
<g ref={vizRef}>
<g
className="viz"
transform={"translate(" + margin.left + "," + 0 + ")"}
>
{contourPaths}
<animated.line
x1={xScale(muMin)}
x2={xScale(muMax)}
y1={0}
y2={0}
className="LogLikMu"
transform={spring.xy.interpolate(
(x, y) => `translate(0, ${yScale(y)})`
)}
/>
<animated.line
y1={yScale(sigma2Min)}
y2={yScale(sigma2Max)}
x1={0}
x2={0}
transform={spring.xy.interpolate(
(x, y) => `translate(${xScale(x)}, 0)`
)}
className="LogLikSigma"
/>
<animated.g
{...bind()}
transform={spring.xy.interpolate(
(x, y) => `translate(${xScale(x)}, ${yScale(y)})`
)}
className="draggable"
>
<circle cx={0} cy={0} r="5" className="logLikX" />
<Tooltip x={0} y={0} equation={eqLogLik(ll)} margin={margin} />
</animated.g>
<path d={linex(props.drawGradientPath)} className="gradientDescent" />
<rect
id="clip-rect"
x="0"
y="0"
width={w}
height={h}
fill="none"
stroke="#fff"
strokeWidth="3px"
/>
</g>
</g>
</svg>
);
}
Example #10
Source File: DateValueLineGraph.jsx From v3-ui with MIT License | 4 votes |
export function DateValueLineGraph(props) {
const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } =
useTooltip()
const { containerRef, TooltipInPortal } = useTooltipInPortal({
detectBounds: true,
scroll: true
})
const { theme } = useContext(ThemeContext)
const circleColor = theme === 'light' ? '#401C94' : '#ffffff'
const id = props.id
const series = props.data
const allData = series.reduce((rec, d) => rec.concat(d), [])
return (
<div className='h-36 mt-6'>
<ParentSize className='max-chart-width mx-auto'>
{({ height, width }) => {
const maxWidth = 1100
const w = Math.min(width, maxWidth)
const maxHeight = 400
const h = Math.min(height, maxHeight)
const xMax = w - margin.left - margin.right
const yMax = h - margin.top - margin.bottom
// scales
const xScale = scaleTime({
range: [0, xMax],
domain: extent(allData, getX)
})
const yScale = scaleLinear({
range: [yMax, 0],
domain: [0, max(allData, getY)]
})
return (
<Fragment key={`${id}-fragment`}>
{tooltipOpen && tooltipData && (
<>
<TooltipInPortal
key={Math.random()}
top={tooltipTop}
left={tooltipLeft}
className='vx-chart-tooltip'
>
{props.valueLabel || 'Value'}:{' '}
<strong>{numberWithCommas(tooltipData.value)}</strong>
<span className='block mt-2'>
Date:{' '}
<strong>
{formatDate(tooltipData.date / 1000, {
short: true,
noTimezone: true
})}
</strong>
</span>
</TooltipInPortal>
</>
)}
<svg ref={containerRef} width={w} height={h}>
{w > 8 &&
series.map((lineData, i) => (
<Group
key={`${id}-group-lines-${i}`}
left={margin.left}
right={margin.right}
top={margin.top}
>
<LinearGradient id='vx-gradient' vertical={false}>
<stop offset='0%' stopColor='#ff9304'></stop>
<stop offset='10%' stopColor='#ff04ea'></stop>
<stop offset='20%' stopColor='#9b4beb'></stop>
<stop offset='30%' stopColor='#0e8dd6'></stop>
<stop offset='40%' stopColor='#3be8ff'></stop>
<stop offset='50%' stopColor='#07d464'></stop>
<stop offset='60%' stopColor='#ebf831'></stop>
<stop offset='78%' stopColor='#ff04ab'></stop>
<stop offset='90%' stopColor='#8933eb'></stop>
<stop offset='100%' stopColor='#3b89ff'></stop>
</LinearGradient>
{lineData?.map((data, j) => {
return (
<LinePath
key={`${id}-group-line-path-${i}-${j}`}
data={lineData}
x={(d) => xScale(getX(d))}
y={(d) => yScale(getY(d))}
stroke={'url(#vx-gradient)'}
strokeWidth={3}
/>
)
})}
{lineData?.map((data, j) => {
return (
<circle
key={`${id}-circle-${i}-${j}`}
r={4}
cx={xScale(getX(data))}
cy={yScale(getY(data))}
stroke={circleColor}
fill={circleColor}
className='cursor-pointer'
onMouseLeave={hideTooltip}
onTouchMove={(event) => {
const coords = localPoint(event.target.ownerSVGElement, event)
showTooltip({
tooltipLeft: coords.x,
tooltipTop: coords.y,
tooltipData: data
})
}}
onMouseMove={(event) => {
const coords = localPoint(event.target.ownerSVGElement, event)
showTooltip({
tooltipLeft: coords.x,
tooltipTop: coords.y,
tooltipData: data
})
}}
/>
)
})}
</Group>
))}
</svg>
</Fragment>
)
}}
</ParentSize>
</div>
)
}
Example #11
Source File: TimeseriesBrush.js From covid19india-react with MIT License | 4 votes |
function TimeseriesBrush({
timeseries,
dates,
currentBrushSelection,
endDate,
lookback,
setBrushSelectionEnd,
setLookback,
animationIndex,
}) {
const chartRef = useRef();
const [wrapperRef, {width, height}] = useMeasure();
const endDateMin =
lookback !== null
? min([
formatISO(addDays(parseIndiaDate(dates[0]), lookback), {
representation: 'date',
}),
endDate,
])
: endDate;
const xScale = useMemo(() => {
const T = dates.length;
// Chart extremes
const chartRight = width - margin.right;
return scaleTime()
.clamp(true)
.domain([
parseIndiaDate(dates[0] || endDate),
parseIndiaDate(dates[T - 1] || endDate),
])
.range([margin.left, chartRight]);
}, [width, endDate, dates]);
useEffect(() => {
if (!width || !height) return;
// Chart extremes
const chartBottom = height - margin.bottom;
const xAxis = (g) =>
g
.attr('class', 'x-axis')
.call(axisBottom(xScale).ticks(numTicksX(width)));
// Switched to daily confirmed instead of cumulative ARD
const timeseriesStacked = stack()
.keys(BRUSH_STATISTICS)
.value((date, statistic) =>
Math.max(0, getStatistic(timeseries[date], 'delta', statistic))
)(dates);
const yScale = scaleLinear()
.clamp(true)
.domain([
0,
max(
timeseriesStacked[timeseriesStacked.length - 1],
([, y1]) => yBufferTop * y1
),
])
.range([chartBottom, margin.top]);
const svg = select(chartRef.current);
const t = svg.transition().duration(D3_TRANSITION_DURATION);
svg
.select('.x-axis')
.attr('pointer-events', 'none')
.style('transform', `translate3d(0, ${chartBottom}px, 0)`)
.transition(t)
.call(xAxis);
const areaPath = area()
.curve(curveMonotoneX)
.x((d) => xScale(parseIndiaDate(d.data)))
.y0((d) => yScale(d[0]))
.y1((d) => yScale(d[1]));
svg
.select('.trend-areas')
.selectAll('.trend-area')
.data(timeseriesStacked)
.join(
(enter) =>
enter
.append('path')
.attr('class', 'trend-area')
.attr('fill', ({key}) => STATISTIC_CONFIGS[key].color)
.attr('fill-opacity', 0.4)
.attr('stroke', ({key}) => STATISTIC_CONFIGS[key].color)
.attr('d', areaPath)
.attr('pointer-events', 'none'),
(update) =>
update
.transition(t)
.attrTween('d', function (date) {
const previous = select(this).attr('d');
const current = areaPath(date);
return interpolatePath(previous, current);
})
.selection()
);
}, [dates, width, height, xScale, timeseries]);
const defaultSelection = currentBrushSelection.map((date) =>
xScale(parseIndiaDate(date))
);
const brush = useMemo(() => {
if (!width || !height) return;
// Chart extremes
const chartRight = width - margin.right;
const chartBottom = height - margin.bottom;
const brush = brushX()
.extent([
[margin.left, margin.top],
[chartRight, chartBottom],
])
.handleSize(20);
return brush;
}, [width, height]);
const brushed = useCallback(
({sourceEvent, selection}) => {
if (!sourceEvent) return;
const [brushStartDate, brushEndDate] = selection.map(xScale.invert);
ReactDOM.unstable_batchedUpdates(() => {
setBrushSelectionEnd(formatISO(brushEndDate, {representation: 'date'}));
setLookback(differenceInDays(brushEndDate, brushStartDate));
});
},
[xScale, setBrushSelectionEnd, setLookback]
);
const beforebrushstarted = useCallback(
(event) => {
const svg = select(chartRef.current);
const selection = brushSelection(svg.select('.brush').node());
if (!selection) return;
const dx = selection[1] - selection[0];
const [[cx]] = pointers(event);
const [x0, x1] = [cx - dx / 2, cx + dx / 2];
const [X0, X1] = xScale.range();
svg
.select('.brush')
.call(
brush.move,
x1 > X1 ? [X1 - dx, X1] : x0 < X0 ? [X0, X0 + dx] : [x0, x1]
);
},
[brush, xScale]
);
const brushended = useCallback(
({sourceEvent, selection}) => {
if (!sourceEvent || !selection) return;
const domain = selection
.map(xScale.invert)
.map((date) => formatISO(date, {representation: 'date'}));
const svg = select(chartRef.current);
svg
.select('.brush')
.call(
brush.move,
domain.map((date) => xScale(parseIndiaDate(date)))
)
.call((g) => g.select('.overlay').attr('cursor', 'pointer'));
},
[brush, xScale]
);
useEffect(() => {
if (!brush) return;
brush.on('start brush', brushed).on('end', brushended);
const svg = select(chartRef.current);
svg
.select('.brush')
.call(brush)
.call((g) =>
g
.select('.overlay')
.attr('cursor', 'pointer')
.datum({type: 'selection'})
.on('mousedown touchstart', beforebrushstarted)
);
}, [brush, brushed, brushended, beforebrushstarted]);
useEffect(() => {
if (!brush) return;
const svg = select(chartRef.current);
svg.select('.brush').call(brush.move, defaultSelection);
}, [brush, defaultSelection]);
const handleWheel = (event) => {
if (event.deltaX) {
setBrushSelectionEnd(
max([
endDateMin,
dates[
Math.max(
0,
Math.min(
dates.length - 1,
dates.indexOf(currentBrushSelection[1]) +
Math.sign(event.deltaX) * brushWheelDelta
)
)
],
])
);
}
};
return (
<div className="Timeseries">
<div
className={classnames('svg-parent is-brush fadeInUp')}
ref={wrapperRef}
onWheel={handleWheel}
style={{animationDelay: `${animationIndex * 250}ms`}}
>
<svg ref={chartRef} preserveAspectRatio="xMidYMid meet">
<defs>
<clipPath id="clipPath">
<rect
x={0}
y={`${margin.top}`}
width={width}
height={`${Math.max(0, height - margin.bottom)}`}
/>
</clipPath>
<mask id="mask">
<rect
x={0}
y={`${margin.top}`}
width={width}
height={`${Math.max(0, height - margin.bottom)}`}
fill="hsl(0, 0%, 40%)"
/>
<use href="#selection" fill="white" />
</mask>
</defs>
<g className="brush" clipPath="url(#clipPath)">
<g mask="url(#mask)">
<rect className="overlay" />
<g className="trend-areas" />
<rect className="selection" id="selection" />
</g>
</g>
<g className="x-axis" />
</svg>
</div>
</div>
);
}
Example #12
Source File: Minigraphs.js From covid19india-react with MIT License | 4 votes |
function Minigraphs({timeseries, date: timelineDate}) {
const refs = useRef([]);
const endDate = timelineDate || getIndiaDateYesterdayISO();
let [wrapperRef, {width}] = useMeasure();
width = Math.min(width, maxWidth);
const dates = useMemo(() => {
const pastDates = Object.keys(timeseries || {}).filter(
(date) => date <= endDate
);
const lastDate = pastDates[pastDates.length - 1];
const cutOffDateLower = formatISO(
subDays(parseIndiaDate(lastDate), MINIGRAPH_LOOKBACK_DAYS),
{representation: 'date'}
);
return pastDates.filter((date) => date >= cutOffDateLower);
}, [endDate, timeseries]);
const getMinigraphStatistic = useCallback(
(date, statistic) => {
return getStatistic(timeseries?.[date], 'delta', statistic);
},
[timeseries]
);
useEffect(() => {
if (!width) return;
const T = dates.length;
const chartRight = width - margin.right;
const chartBottom = height - margin.bottom;
const xScale = scaleTime()
.clamp(true)
.domain([
parseIndiaDate(dates[0] || endDate),
parseIndiaDate(dates[T - 1]) || endDate,
])
.range([margin.left, chartRight]);
refs.current.forEach((ref, index) => {
const svg = select(ref);
const statistic = LEVEL_STATISTICS[index];
const color = STATISTIC_CONFIGS[statistic].color;
const dailyMaxAbs = max(dates, (date) =>
Math.abs(getMinigraphStatistic(date, statistic))
);
const yScale = scaleLinear()
.clamp(true)
.domain([-dailyMaxAbs, dailyMaxAbs])
.range([chartBottom, margin.top]);
const linePath = line()
.curve(curveMonotoneX)
.x((date) => xScale(parseIndiaDate(date)))
.y((date) => yScale(getMinigraphStatistic(date, statistic)));
let pathLength;
svg
.selectAll('path')
.data(T ? [dates] : [])
.join(
(enter) =>
enter
.append('path')
.attr('fill', 'none')
.attr('stroke', color + '99')
.attr('stroke-width', 2.5)
.attr('d', linePath)
.attr('stroke-dasharray', function () {
return (pathLength = this.getTotalLength());
})
.call((enter) =>
enter
.attr('stroke-dashoffset', pathLength)
.transition()
.delay(100)
.duration(2500)
.attr('stroke-dashoffset', 0)
),
(update) =>
update
.attr('stroke-dasharray', null)
.transition()
.duration(500)
.attrTween('d', function (date) {
const previous = select(this).attr('d');
const current = linePath(date);
return interpolatePath(previous, current);
})
.selection()
);
svg
.selectAll('circle')
.data(T ? [dates[T - 1]] : [])
.join(
(enter) =>
enter
.append('circle')
.attr('fill', color)
.attr('r', 2.5)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) =>
yScale(getMinigraphStatistic(date, statistic))
)
.style('opacity', 0)
.call((enter) =>
enter
.transition()
.delay(2100)
.duration(500)
.style('opacity', 1)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) =>
yScale(getMinigraphStatistic(date, statistic))
)
),
(update) =>
update
.transition()
.duration(500)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) =>
yScale(getMinigraphStatistic(date, statistic))
)
.style('opacity', 1)
.selection()
);
});
}, [endDate, dates, width, getMinigraphStatistic]);
return (
<div className="Minigraph">
{LEVEL_STATISTICS.map((statistic, index) => (
<div
key={statistic}
className={classnames('svg-parent')}
ref={index === 0 ? wrapperRef : null}
style={{width: `calc(${100 / LEVEL_STATISTICS.length}%)`}}
>
<svg
ref={(el) => {
refs.current[index] = el;
}}
preserveAspectRatio="xMidYMid meet"
width={width}
height={height}
/>
</div>
))}
</div>
);
}
Example #13
Source File: MapVisualizer.js From covid19india-react with MIT License | 4 votes |
function MapVisualizer({ mapCode, isDistrictView, mapViz, data, regionHighlighted, setRegionHighlighted, statistic, getMapStatistic, transformStatistic, noDistrictData, }) { const {t} = useTranslation(); const svgRef = useRef(null); const mapMeta = MAP_META[mapCode]; const history = useHistory(); const {data: geoData} = useSWR( mapMeta.geoDataFile, async (file) => { return await json(file); }, {suspense: false, revalidateOnFocus: false} ); const statisticTotal = useMemo(() => { return getMapStatistic(data[mapCode]); }, [data, mapCode, getMapStatistic]); const statisticConfig = STATISTIC_CONFIGS[statistic]; const strokeColor = useCallback( (alpha) => (statisticConfig?.color || '#343a40') + alpha, [statisticConfig] ); const features = useMemo(() => { if (!geoData) return null; const featuresWrap = !isDistrictView ? feature(geoData, geoData.objects.states).features : mapMeta.mapType === MAP_TYPES.COUNTRY && mapViz !== MAP_VIZS.CHOROPLETH ? [ ...feature(geoData, geoData.objects.states).features, ...feature(geoData, geoData.objects.districts).features, ] : feature(geoData, geoData.objects.districts).features; // Add id to each feature return featuresWrap.map((feature) => { const district = feature.properties.district; const state = feature.properties.st_nm; const obj = Object.assign({}, feature); obj.id = `${mapCode}-${state}${district ? '-' + district : ''}`; return obj; }); }, [geoData, mapCode, isDistrictView, mapViz, mapMeta]); const districtsSet = useMemo(() => { if (!geoData || !isDistrictView) return {}; return feature(geoData, geoData.objects.districts).features.reduce( (stateCodes, feature) => { const stateCode = STATE_CODES[feature.properties.st_nm]; if (!stateCodes[stateCode]) { stateCodes[stateCode] = new Set(); } stateCodes[stateCode].add(feature.properties.district); return stateCodes; }, {} ); }, [geoData, isDistrictView]); const statisticMax = useMemo(() => { const stateCodes = Object.keys(data).filter( (stateCode) => stateCode !== 'TT' && Object.keys(MAP_META).includes(stateCode) ); if (!isDistrictView) { return max(stateCodes, (stateCode) => transformStatistic(getMapStatistic(data[stateCode])) ); } else { const districtData = stateCodes.reduce((res, stateCode) => { const districts = Object.keys(data[stateCode]?.districts || []).filter( (districtName) => (districtsSet?.[stateCode] || new Set()).has(districtName) || (mapViz !== MAP_VIZS.CHOROPLETH && districtName === UNKNOWN_DISTRICT_KEY) ); res.push( ...districts.map((districtName) => transformStatistic( getMapStatistic(data[stateCode].districts[districtName]) ) ) ); return res; }, []); return max(districtData); } }, [ data, isDistrictView, getMapStatistic, mapViz, districtsSet, transformStatistic, ]); const mapScale = useMemo(() => { if (mapViz === MAP_VIZS.BUBBLE) { // No negative values return scaleSqrt([0, Math.max(1, statisticMax || 0)], [0, 40]) .clamp(true) .nice(3); } else if (mapViz === MAP_VIZS.SPIKE) { return scaleLinear([0, Math.max(1, statisticMax || 0)], [0, 80]) .clamp(true) .nice(3); } else if (STATISTIC_CONFIGS[statistic]?.mapConfig?.colorScale) { return STATISTIC_CONFIGS[statistic].mapConfig.colorScale; } else { // No negative values return scaleSequential( [0, Math.max(1, statisticMax || 0)], colorInterpolator(statistic) ).clamp(true); } }, [mapViz, statistic, statisticMax]); const fillColor = useCallback( (d) => { if (mapViz === MAP_VIZS.CHOROPLETH) { const stateCode = STATE_CODES[d.properties.st_nm]; const district = d.properties.district; const stateData = data[stateCode]; const districtData = stateData?.districts?.[district]; const n = transformStatistic( getMapStatistic(district ? districtData : stateData) ); const color = n ? mapScale(n) : '#ffffff00'; return color; } }, [mapViz, data, mapScale, getMapStatistic, transformStatistic] ); const populateTexts = useCallback( (regionSelection) => { regionSelection.select('title').text((d) => { if (mapViz !== MAP_VIZS.CHOROPLETH && !statisticConfig?.nonLinear) { const state = d.properties.st_nm; const stateCode = STATE_CODES[state]; const district = d.properties.district; const stateData = data[stateCode]; const districtData = stateData?.districts?.[district]; let n; if (district) n = getMapStatistic(districtData); else n = getMapStatistic(stateData); return `${formatNumber( 100 * (n / (statisticTotal || 0.001)), '%' )} from ${toTitleCase(district ? district : state)}`; } }); }, [mapViz, data, getMapStatistic, statisticTotal, statisticConfig] ); const onceTouchedRegion = useRef(null); // Reset on tapping outside map useEffect(() => { const svg = select(svgRef.current); svg.attr('pointer-events', 'auto').on('click', () => { onceTouchedRegion.current = null; setRegionHighlighted({ stateCode: mapCode, districtName: null, }); }); }, [mapCode, setRegionHighlighted]); const path = useMemo(() => { if (!geoData) return null; return geoPath(geoIdentity()); }, [geoData]); // Choropleth useEffect(() => { if (!geoData) return; const svg = select(svgRef.current); const T = transition().duration(D3_TRANSITION_DURATION); svg .select('.regions') .selectAll('path') .data(mapViz === MAP_VIZS.CHOROPLETH ? features : [], (d) => d.id) .join( (enter) => enter .append('path') .attr('d', path) .attr('stroke-width', 1.8) .attr('stroke-opacity', 0) .style('cursor', 'pointer') .on('mouseenter', (event, d) => { if (onceTouchedRegion.current) return; setRegionHighlighted({ stateCode: STATE_CODES[d.properties.st_nm], districtName: d.properties.district, }); }) .on('pointerdown', (event, d) => { if (onceTouchedRegion.current === d) onceTouchedRegion.current = null; else onceTouchedRegion.current = d; setRegionHighlighted({ stateCode: STATE_CODES[d.properties.st_nm], districtName: d.properties.district, }); }) .attr('fill', '#fff0') .attr('stroke', '#fff0'), (update) => update, (exit) => exit .transition(T) .attr('stroke', '#fff0') .attr('fill', '#fff0') .remove() ) .attr('pointer-events', 'all') .on('click', (event, d) => { event.stopPropagation(); const stateCode = STATE_CODES[d.properties.st_nm]; if ( onceTouchedRegion.current || mapMeta.mapType === MAP_TYPES.STATE || !data[stateCode]?.districts ) return; // Disable pointer events till the new map is rendered svg.attr('pointer-events', 'none'); svg.select('.regions').selectAll('path').attr('pointer-events', 'none'); // Switch map history.push( `/state/${stateCode}${window.innerWidth < 769 ? '#MapExplorer' : ''}` ); }) .call((sel) => { sel .transition(T) .attr('fill', fillColor) .attr('stroke', strokeColor.bind(this, '')); }); }, [ mapViz, data, features, fillColor, geoData, history, mapMeta.mapType, path, setRegionHighlighted, strokeColor, ]); const sortedFeatures = useMemo(() => { if (mapViz === MAP_VIZS.CHOROPLETH) { return []; } else { return (features || []) .map((feature) => { const stateCode = STATE_CODES[feature.properties.st_nm]; const districtName = feature.properties.district; const stateData = data[stateCode]; if (!isDistrictView) { feature.value = getMapStatistic(stateData); } else { const districtData = stateData?.districts?.[districtName]; if (districtName) feature.value = getMapStatistic(districtData); else feature.value = getMapStatistic( stateData?.districts?.[UNKNOWN_DISTRICT_KEY] ); } return feature; }) .filter((feature) => feature.value > 0) .sort((featureA, featureB) => featureB.value - featureB.value); } }, [mapViz, isDistrictView, getMapStatistic, features, data]); // Bubble useEffect(() => { const svg = select(svgRef.current); const T = transition().duration(D3_TRANSITION_DURATION); const regionSelection = svg .select('.circles') .selectAll('circle') .data( mapViz === MAP_VIZS.BUBBLE ? sortedFeatures : [], (feature) => feature.id ) .join( (enter) => enter .append('circle') .attr( 'transform', (feature) => `translate(${path.centroid(feature)})` ) .attr('fill-opacity', 0.25) .style('cursor', 'pointer') .attr('pointer-events', 'all') .call((enter) => { enter.append('title'); }), (update) => update, (exit) => exit.call((exit) => exit.transition(T).attr('r', 0).remove()) ) .on('mouseenter', (event, feature) => { if (onceTouchedRegion.current) return; setRegionHighlighted({ stateCode: STATE_CODES[feature.properties.st_nm], districtName: !isDistrictView ? null : feature.properties.district || UNKNOWN_DISTRICT_KEY, }); }) .on('pointerdown', (event, feature) => { if (onceTouchedRegion.current === feature) onceTouchedRegion.current = null; else onceTouchedRegion.current = feature; setRegionHighlighted({ stateCode: STATE_CODES[feature.properties.st_nm], districtName: !isDistrictView ? null : feature.properties.district || UNKNOWN_DISTRICT_KEY, }); }) .on('click', (event, feature) => { event.stopPropagation(); if (onceTouchedRegion.current || mapMeta.mapType === MAP_TYPES.STATE) return; history.push( `/state/${STATE_CODES[feature.properties.st_nm]}${ window.innerWidth < 769 ? '#MapExplorer' : '' }` ); }) .call((sel) => { sel .transition(T) .attr('fill', statisticConfig.color + '70') .attr('stroke', statisticConfig.color + '70') .attr('r', (feature) => mapScale(feature.value)); }); window.requestIdleCallback(() => { populateTexts(regionSelection); }); }, [ mapMeta.mapType, mapViz, isDistrictView, sortedFeatures, history, mapScale, path, setRegionHighlighted, populateTexts, statisticConfig, getMapStatistic, ]); // Spike (Note: bad unmodular code) useEffect(() => { const svg = select(svgRef.current); const T = transition().duration(D3_TRANSITION_DURATION); const regionSelection = svg .select('.spikes') .selectAll('path') .data( mapViz === MAP_VIZS.SPIKE ? sortedFeatures : [], (feature) => feature.id, (feature) => feature.id ) .join( (enter) => enter .append('path') .attr( 'transform', (feature) => `translate(${path.centroid(feature)})` ) .attr('opacity', 0) .attr('fill-opacity', 0.25) .style('cursor', 'pointer') .attr('pointer-events', 'all') .attr('d', spike(0)) .call((enter) => { enter.append('title'); }), (update) => update, (exit) => exit.call((exit) => exit.transition(T).attr('opacity', 0).attr('d', spike(0)).remove() ) ) .on('mouseenter', (event, feature) => { if (onceTouchedRegion.current) return; setRegionHighlighted({ stateCode: STATE_CODES[feature.properties.st_nm], districtName: !isDistrictView ? null : feature.properties.district || UNKNOWN_DISTRICT_KEY, }); }) .on('pointerdown', (event, feature) => { if (onceTouchedRegion.current === feature) onceTouchedRegion.current = null; else onceTouchedRegion.current = feature; setRegionHighlighted({ stateCode: STATE_CODES[feature.properties.st_nm], districtName: !isDistrictView ? null : feature.properties.district || UNKNOWN_DISTRICT_KEY, }); }) .on('click', (event, feature) => { event.stopPropagation(); if (onceTouchedRegion.current || mapMeta.mapType === MAP_TYPES.STATE) return; history.push( `/state/${STATE_CODES[feature.properties.st_nm]}${ window.innerWidth < 769 ? '#MapExplorer' : '' }` ); }) .call((sel) => { sel .transition(T) .attr('opacity', 1) .attr('fill', statisticConfig.color + '70') .attr('stroke', statisticConfig.color + '70') .attr('d', (feature) => spike(mapScale(feature.value))); }); window.requestIdleCallback(() => { populateTexts(regionSelection); }); }, [ mapMeta.mapType, mapViz, isDistrictView, sortedFeatures, history, mapScale, path, setRegionHighlighted, populateTexts, statisticConfig, getMapStatistic, ]); // Boundaries useEffect(() => { if (!geoData) return; const svg = select(svgRef.current); const T = transition().duration(D3_TRANSITION_DURATION); let meshStates = []; let meshDistricts = []; if (mapMeta.mapType === MAP_TYPES.COUNTRY) { meshStates = [mesh(geoData, geoData.objects.states)]; meshStates[0].id = `${mapCode}-states`; } if ( mapMeta.mapType === MAP_TYPES.STATE || (isDistrictView && mapViz === MAP_VIZS.CHOROPLETH) ) { // Add id to mesh meshDistricts = [mesh(geoData, geoData.objects.districts)]; meshDistricts[0].id = `${mapCode}-districts`; } svg .select('.state-borders') .attr('fill', 'none') .attr('stroke-width', 1.5) .selectAll('path') .data(meshStates, (d) => d.id) .join( (enter) => enter.append('path').attr('d', path).attr('stroke', '#fff0'), (update) => update, (exit) => exit.transition(T).attr('stroke', '#fff0').remove() ) .transition(T) .attr('stroke', strokeColor.bind(this, '40')); svg .select('.district-borders') .attr('fill', 'none') .attr('stroke-width', 1.5) .selectAll('path') .data(meshDistricts, (d) => d.id) .join( (enter) => enter .append('path') .attr('d', path) .attr('d', path) .attr('stroke', '#fff0'), (update) => update, (exit) => exit.transition(T).attr('stroke', '#fff0').remove() ) .transition(T) .attr('stroke', strokeColor.bind(this, '40')); }, [ geoData, mapMeta, mapCode, mapViz, isDistrictView, statistic, path, strokeColor, ]); // Highlight useEffect(() => { const stateCode = regionHighlighted.stateCode; const stateName = STATE_NAMES[stateCode]; const district = regionHighlighted.districtName; const svg = select(svgRef.current); if (mapViz === MAP_VIZS.BUBBLE) { svg .select('.circles') .selectAll('circle') .attr('fill-opacity', (d) => { const highlighted = stateName === d.properties.st_nm && ((!district && stateCode !== mapCode) || district === d.properties?.district || !isDistrictView || (district === UNKNOWN_DISTRICT_KEY && !d.properties.district)); return highlighted ? 1 : 0.25; }); } else if (mapViz === MAP_VIZS.SPIKE) { svg .select('.spikes') .selectAll('path') .attr('fill-opacity', (d) => { const highlighted = stateName === d.properties.st_nm && ((!district && stateCode !== mapCode) || district === d.properties?.district || !isDistrictView || (district === UNKNOWN_DISTRICT_KEY && !d.properties.district)); return highlighted ? 1 : 0.25; }); } else { svg .select('.regions') .selectAll('path') .each(function (d) { const highlighted = stateName === d.properties.st_nm && ((!district && stateCode !== mapCode) || district === d.properties?.district || !isDistrictView); if (highlighted) this.parentNode.appendChild(this); select(this).attr('stroke-opacity', highlighted ? 1 : 0); }); } }, [ geoData, data, mapCode, isDistrictView, mapViz, regionHighlighted.stateCode, regionHighlighted.districtName, statistic, ]); return ( <> <div className="svg-parent"> <svg id="chart" className={classnames({ zone: !!statisticConfig?.mapConfig?.colorScale, })} viewBox={`0 0 ${MAP_DIMENSIONS[0]} ${MAP_DIMENSIONS[1]}`} preserveAspectRatio="xMidYMid meet" ref={svgRef} > <g className="regions" /> <g className="state-borders" /> <g className="district-borders" /> <g className="circles" /> <g className="spikes" /> </svg> {noDistrictData && statisticConfig?.hasPrimary && ( <div className={classnames('disclaimer', `is-${statistic}`)}> <AlertIcon /> <span> {t('District-wise data not available in state bulletin')} </span> </div> )} </div> {mapScale && <MapLegend {...{data, statistic, mapViz, mapScale}} />} <svg style={{position: 'absolute', height: 0}}> <defs> <filter id="balance-color" colorInterpolationFilters="sRGB"> <feColorMatrix type="matrix" values="0.91372549 0 0 0 0.08627451 0 0.91372549 0 0 0.08627451 0 0 0.854901961 0 0.145098039 0 0 0 1 0" /> </filter> </defs> </svg> </> ); }
Example #14
Source File: symbol.js From t-viSNE with MIT License | 4 votes |
export default function symbol() {
let scale = scaleLinear(),
shape = "path",
shapeWidth = 15,
shapeHeight = 15,
shapeRadius = 10,
shapePadding = 5,
cells = [5],
cellFilter,
labels = [],
classPrefix = "",
title = "",
locale = helper.d3_defaultLocale,
specifier = helper.d3_defaultFormatSpecifier,
labelAlign = "middle",
labelOffset = 10,
labelDelimiter = helper.d3_defaultDelimiter,
labelWrap,
orient = "vertical",
ascending = false,
titleWidth,
legendDispatcher = dispatch("cellover", "cellout", "cellclick")
function legend(svg) {
const type = helper.d3_calcType(
scale,
ascending,
cells,
labels,
locale.format(specifier),
labelDelimiter
),
legendG = svg.selectAll("g").data([scale])
if (cellFilter) {
helper.d3_filterCells(type, cellFilter)
}
legendG
.enter()
.append("g")
.attr("class", classPrefix + "legendCells")
let cell = svg
.select("." + classPrefix + "legendCells")
.selectAll("." + classPrefix + "cell")
.data(type.data)
const cellEnter = cell
.enter()
.append("g")
.attr("class", classPrefix + "cell")
cellEnter.append(shape).attr("class", classPrefix + "swatch")
let shapes = svg.selectAll("g." + classPrefix + "cell " + shape + "." + classPrefix + "swatch")
//add event handlers
helper.d3_addEvents(cellEnter, legendDispatcher)
//remove old shapes
cell
.exit()
.transition()
.style("opacity", 0)
.remove()
shapes
.exit()
.transition()
.style("opacity", 0)
.remove()
shapes = shapes.merge(shapes)
helper.d3_drawShapes(
shape,
shapes,
shapeHeight,
shapeWidth,
shapeRadius,
type.feature
)
const text = helper.d3_addText(
svg,
cellEnter,
type.labels,
classPrefix,
labelWrap
)
// we need to merge the selection, otherwise changes in the legend (e.g. change of orientation) are applied only to the new cells and not the existing ones.
cell = cellEnter.merge(cell)
// sets placement
const textSize = text.nodes().map(d => d.getBBox()),
shapeSize = shapes.nodes().map(d => d.getBBox())
const maxH = max(shapeSize, d => d.height),
maxW = max(shapeSize, d => d.width)
let cellTrans,
textTrans,
textAlign = labelAlign == "start" ? 0 : labelAlign == "middle" ? 0.5 : 1
//positions cells and text
if (orient === "vertical") {
const cellSize = textSize.map((d, i) => Math.max(maxH, d.height))
cellTrans = (d, i) => {
const height = sum(cellSize.slice(0, i))
return `translate(0, ${height + i * shapePadding} )`
}
textTrans = (d, i) => `translate( ${maxW + labelOffset},
${shapeSize[i].y + shapeSize[i].height / 2 + 5})`
} else if (orient === "horizontal") {
cellTrans = (d, i) => `translate( ${i * (maxW + shapePadding)},0)`
textTrans = (d, i) => `translate( ${shapeSize[i].width * textAlign +
shapeSize[i].x},
${maxH + labelOffset})`
}
helper.d3_placement(orient, cell, cellTrans, text, textTrans, labelAlign)
helper.d3_title(svg, title, classPrefix, titleWidth)
cell.transition().style("opacity", 1)
}
legend.scale = function(_) {
if (!arguments.length) return scale
scale = _
return legend
}
legend.cells = function(_) {
if (!arguments.length) return cells
if (_.length > 1 || _ >= 2) {
cells = _
}
return legend
}
legend.cellFilter = function(_) {
if (!arguments.length) return cellFilter
cellFilter = _
return legend
}
legend.shapePadding = function(_) {
if (!arguments.length) return shapePadding
shapePadding = +_
return legend
}
legend.labels = function(_) {
if (!arguments.length) return labels
labels = _
return legend
}
legend.labelAlign = function(_) {
if (!arguments.length) return labelAlign
if (_ == "start" || _ == "end" || _ == "middle") {
labelAlign = _
}
return legend
}
legend.locale = function(_) {
if (!arguments.length) return locale
locale = formatLocale(_)
return legend
}
legend.labelFormat = function(_) {
if (!arguments.length) return legend.locale().format(specifier)
specifier = formatSpecifier(_)
return legend
}
legend.labelOffset = function(_) {
if (!arguments.length) return labelOffset
labelOffset = +_
return legend
}
legend.labelDelimiter = function(_) {
if (!arguments.length) return labelDelimiter
labelDelimiter = _
return legend
}
legend.labelWrap = function(_) {
if (!arguments.length) return labelWrap
labelWrap = _
return legend
}
legend.orient = function(_) {
if (!arguments.length) return orient
_ = _.toLowerCase()
if (_ == "horizontal" || _ == "vertical") {
orient = _
}
return legend
}
legend.ascending = function(_) {
if (!arguments.length) return ascending
ascending = !!_
return legend
}
legend.classPrefix = function(_) {
if (!arguments.length) return classPrefix
classPrefix = _
return legend
}
legend.title = function(_) {
if (!arguments.length) return title
title = _
return legend
}
legend.titleWidth = function(_) {
if (!arguments.length) return titleWidth
titleWidth = _
return legend
}
legend.on = function() {
const value = legendDispatcher.on.apply(legendDispatcher, arguments)
return value === legendDispatcher ? legend : value
}
return legend
}
Example #15
Source File: area-chart.js From website with Apache License 2.0 | 4 votes |
AreaChart = ({
annotations,
data,
fill,
height,
labelOrder,
marginBottom,
marginLeft,
marginRight,
marginTop,
xTicks,
width,
yFormat,
yMax,
yTicks,
showTicks,
focusable,
dateExtent,
renderTooltipContents,
}) => {
const grouped = nest()
.key(d => d.label)
.entries(data)
const sorted = !labelOrder
? grouped
: labelOrder
.map(label => {
const match = grouped.find(d => d.key === label)
return match
})
.filter(d => d)
const domain = dateExtent || extent(data, d => d.date)
const valueMax = max(data, d => d.value)
const totalXMargin = marginLeft + marginRight
const totalYMargin = marginTop + marginBottom
const fillFn = typeof fill === 'string' ? fill : d => fill(d.key)
const xScale = scaleTime()
.domain(domain)
.range([0, width - totalXMargin])
const yScale = scaleLinear()
.domain([0, yMax || valueMax])
.range([height - totalYMargin, 0])
const a = area()
.x(d => xScale(d.date))
.y0(d => yScale(d.value))
.y1(height - totalYMargin)
const [tooltip, setTooltip] = useState(null)
function svgPoint(svg, x, y) {
const pt = svg.createSVGPoint()
pt.x = x
pt.y = y
return pt.matrixTransform(svg.getScreenCTM().inverse())
}
const handleMouseMove = event => {
const isTouchEvent = !event.clientX
if (!renderTooltipContents) return
const eventX = isTouchEvent ? event.touches[0].clientX : event.clientX
const eventY = isTouchEvent ? event.touches[0].clientY : event.clientY
const result = svgPoint(event.currentTarget, eventX, eventY)
const date = xScale.invert(result.x - marginLeft + 8)
date.setHours(0, 0, 0)
setTooltip({
top: isTouchEvent ? eventY - 130 : eventY + 10,
left: isTouchEvent ? eventX - 80 : eventX + 5,
date,
})
}
const handleMouseLeave = () => setTooltip(null)
const dateMap = useMemo(
() =>
merge(
...data.map(d => ({
[d.date]: {
date: d.date,
[d.label]: d.value,
},
})),
),
[data],
)
const handleTouchEndCapture = event => {
setTooltip(null)
event.preventDefault()
}
return (
<>
<svg
className={chartStyles.chart}
viewBox={`0 0 ${width} ${height}`}
focusable={focusable}
aria-hidden
onTouchStart={handleMouseMove}
onTouchEndCapture={handleTouchEndCapture}
onMouseMoveCapture={handleMouseMove}
onMouseLeave={handleMouseLeave}
>
{showTicks ? (
<g transform={`translate(${marginLeft} ${marginTop})`}>
<g transform={`translate(0 ${height - totalYMargin})`}>
{xScale.ticks(xTicks).map(tick => (
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
key={tick}
x={xScale(tick)}
y={20}
>
{formatDate(tick)}
</text>
))}
</g>
<g>
{yScale.ticks(yTicks).map((tick, i) => (
<g key={tick}>
{/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
<svg
y={yScale(tick) + 4}
x="-10"
className={chartStyles.yTickLabel}
>
<text className={chartStyles.label}>
{yFormat
? yFormat(tick, i, yScale.ticks(yTicks).length)
: formatNumber(tick)}
</text>
</svg>
<line
className={chartStyles.gridLine}
x1={0}
x2={width - totalXMargin}
y1={yScale(tick)}
y2={yScale(tick)}
/>
</g>
))}
</g>
</g>
) : (
<line
className={chartStyles.gridLine}
x1={0}
x2={width}
y1={height - 1}
y2={height - 1}
/>
)}
<g transform={`translate(${marginLeft} ${marginTop})`}>
{sorted.map(d => (
<path key={d.key} d={a(d.values)} opacity={0.8} fill={fillFn(d)} />
))}
</g>
{annotations && (
<g transform={`translate(${marginLeft} ${marginTop})`}>
{annotations.map(d => (
<line
key={d.date}
stroke="black"
strokeWidth="2px"
x1={xScale(d.date) - 1}
x2={xScale(d.date) - 1}
y1="0"
y2={height - marginTop - marginBottom}
/>
))}
</g>
)}
</svg>
{tooltip && renderTooltipContents && dateMap[tooltip.date] && (
<Tooltip {...tooltip}>
{renderTooltipContents(dateMap[tooltip.date])}
</Tooltip>
)}
</>
)
}
Example #16
Source File: indexRollupNext.js From t-viSNE with MIT License | 4 votes |
function symbol() {
var scale = scaleLinear(),
shape = "path",
shapeWidth = 15,
shapeHeight = 15,
shapeRadius = 10,
shapePadding = 5,
cells = [5],
cellFilter = void 0,
labels = [],
classPrefix = "",
title = "",
locale = helper.d3_defaultLocale,
specifier = helper.d3_defaultFormatSpecifier,
labelAlign = "middle",
labelOffset = 10,
labelDelimiter = helper.d3_defaultDelimiter,
labelWrap = void 0,
orient = "vertical",
ascending = false,
titleWidth = void 0,
legendDispatcher = dispatch("cellover", "cellout", "cellclick");
function legend(svg) {
var type = helper.d3_calcType(scale, ascending, cells, labels, locale.format(specifier), labelDelimiter),
legendG = svg.selectAll("g").data([scale]);
if (cellFilter) {
helper.d3_filterCells(type, cellFilter);
}
legendG.enter().append("g").attr("class", classPrefix + "legendCells");
var cell = svg.select("." + classPrefix + "legendCells").selectAll("." + classPrefix + "cell").data(type.data);
var cellEnter = cell.enter().append("g").attr("class", classPrefix + "cell");
cellEnter.append(shape).attr("class", classPrefix + "swatch");
var shapes = svg.selectAll("g." + classPrefix + "cell " + shape + "." + classPrefix + "swatch");
//add event handlers
helper.d3_addEvents(cellEnter, legendDispatcher);
//remove old shapes
cell.exit().transition().style("opacity", 0).remove();
shapes.exit().transition().style("opacity", 0).remove();
shapes = shapes.merge(shapes);
helper.d3_drawShapes(shape, shapes, shapeHeight, shapeWidth, shapeRadius, type.feature);
var text = helper.d3_addText(svg, cellEnter, type.labels, classPrefix, labelWrap);
// we need to merge the selection, otherwise changes in the legend (e.g. change of orientation) are applied only to the new cells and not the existing ones.
cell = cellEnter.merge(cell);
// sets placement
var textSize = text.nodes().map(function (d) {
return d.getBBox();
}),
shapeSize = shapes.nodes().map(function (d) {
return d.getBBox();
});
var maxH = max(shapeSize, function (d) {
return d.height;
}),
maxW = max(shapeSize, function (d) {
return d.width;
});
var cellTrans = void 0,
textTrans = void 0,
textAlign = labelAlign == "start" ? 0 : labelAlign == "middle" ? 0.5 : 1;
//positions cells and text
if (orient === "vertical") {
(function () {
var cellSize = textSize.map(function (d, i) {
return Math.max(maxH, d.height);
});
cellTrans = function cellTrans(d, i) {
var height = sum(cellSize.slice(0, i));
return "translate(0, " + (height + i * shapePadding) + " )";
};
textTrans = function textTrans(d, i) {
return "translate( " + (maxW + labelOffset) + ",\n " + (shapeSize[i].y + shapeSize[i].height / 2 + 5) + ")";
};
})();
} else if (orient === "horizontal") {
cellTrans = function cellTrans(d, i) {
return "translate( " + i * (maxW + shapePadding) + ",0)";
};
textTrans = function textTrans(d, i) {
return "translate( " + (shapeSize[i].width * textAlign + shapeSize[i].x) + ",\n " + (maxH + labelOffset) + ")";
};
}
helper.d3_placement(orient, cell, cellTrans, text, textTrans, labelAlign);
helper.d3_title(svg, title, classPrefix, titleWidth);
cell.transition().style("opacity", 1);
}
legend.scale = function (_) {
if (!arguments.length) return scale;
scale = _;
return legend;
};
legend.cells = function (_) {
if (!arguments.length) return cells;
if (_.length > 1 || _ >= 2) {
cells = _;
}
return legend;
};
legend.cellFilter = function (_) {
if (!arguments.length) return cellFilter;
cellFilter = _;
return legend;
};
legend.shapePadding = function (_) {
if (!arguments.length) return shapePadding;
shapePadding = +_;
return legend;
};
legend.labels = function (_) {
if (!arguments.length) return labels;
labels = _;
return legend;
};
legend.labelAlign = function (_) {
if (!arguments.length) return labelAlign;
if (_ == "start" || _ == "end" || _ == "middle") {
labelAlign = _;
}
return legend;
};
legend.locale = function (_) {
if (!arguments.length) return locale;
locale = formatLocale(_);
return legend;
};
legend.labelFormat = function (_) {
if (!arguments.length) return legend.locale().format(specifier);
specifier = formatSpecifier(_);
return legend;
};
legend.labelOffset = function (_) {
if (!arguments.length) return labelOffset;
labelOffset = +_;
return legend;
};
legend.labelDelimiter = function (_) {
if (!arguments.length) return labelDelimiter;
labelDelimiter = _;
return legend;
};
legend.labelWrap = function (_) {
if (!arguments.length) return labelWrap;
labelWrap = _;
return legend;
};
legend.orient = function (_) {
if (!arguments.length) return orient;
_ = _.toLowerCase();
if (_ == "horizontal" || _ == "vertical") {
orient = _;
}
return legend;
};
legend.ascending = function (_) {
if (!arguments.length) return ascending;
ascending = !!_;
return legend;
};
legend.classPrefix = function (_) {
if (!arguments.length) return classPrefix;
classPrefix = _;
return legend;
};
legend.title = function (_) {
if (!arguments.length) return title;
title = _;
return legend;
};
legend.titleWidth = function (_) {
if (!arguments.length) return titleWidth;
titleWidth = _;
return legend;
};
legend.on = function () {
var value = legendDispatcher.on.apply(legendDispatcher, arguments);
return value === legendDispatcher ? legend : value;
};
return legend;
}
Example #17
Source File: indexRollupNext.js From t-viSNE with MIT License | 4 votes |
function size() {
var scale = scaleLinear(),
shape = "rect",
shapeWidth = 15,
shapePadding = 2,
cells = [5],
cellFilter = void 0,
labels = [],
classPrefix = "",
title = "",
locale = helper.d3_defaultLocale,
specifier = helper.d3_defaultFormatSpecifier,
labelOffset = 10,
labelAlign = "middle",
labelDelimiter = helper.d3_defaultDelimiter,
labelWrap = void 0,
orient = "vertical",
ascending = false,
path = void 0,
titleWidth = void 0,
legendDispatcher = dispatch("cellover", "cellout", "cellclick");
function legend(svg) {
var type = helper.d3_calcType(scale, ascending, cells, labels, locale.format(specifier), labelDelimiter),
legendG = svg.selectAll("g").data([scale]);
if (cellFilter) {
helper.d3_filterCells(type, cellFilter);
}
legendG.enter().append("g").attr("class", classPrefix + "legendCells");
var cell = svg.select("." + classPrefix + "legendCells").selectAll("." + classPrefix + "cell").data(type.data);
var cellEnter = cell.enter().append("g").attr("class", classPrefix + "cell");
cellEnter.append(shape).attr("class", classPrefix + "swatch");
var shapes = svg.selectAll("g." + classPrefix + "cell " + shape + "." + classPrefix + "swatch");
//add event handlers
helper.d3_addEvents(cellEnter, legendDispatcher);
cell.exit().transition().style("opacity", 0).remove();
shapes.exit().transition().style("opacity", 0).remove();
shapes = shapes.merge(shapes);
//creates shape
if (shape === "line") {
helper.d3_drawShapes(shape, shapes, 0, shapeWidth);
shapes.attr("stroke-width", type.feature);
} else {
helper.d3_drawShapes(shape, shapes, type.feature, type.feature, type.feature, path);
}
var text = helper.d3_addText(svg, cellEnter, type.labels, classPrefix, labelWrap);
// we need to merge the selection, otherwise changes in the legend (e.g. change of orientation) are applied only to the new cells and not the existing ones.
cell = cellEnter.merge(cell);
//sets placement
var textSize = text.nodes().map(function (d) {
return d.getBBox();
}),
shapeSize = shapes.nodes().map(function (d, i) {
var bbox = d.getBBox();
var stroke = scale(type.data[i]);
if (shape === "line" && orient === "horizontal") {
bbox.height = bbox.height + stroke;
} else if (shape === "line" && orient === "vertical") {
bbox.width = bbox.width;
}
return bbox;
});
var maxH = max(shapeSize, function (d) {
return d.height + d.y;
}),
maxW = max(shapeSize, function (d) {
return d.width + d.x;
});
var cellTrans = void 0,
textTrans = void 0,
textAlign = labelAlign == "start" ? 0 : labelAlign == "middle" ? 0.5 : 1;
//positions cells and text
if (orient === "vertical") {
(function () {
var cellSize = textSize.map(function (d, i) {
return Math.max(d.height, shapeSize[i].height);
});
var y = shape == "circle" || shape == "line" ? shapeSize[0].height / 2 : 0;
cellTrans = function cellTrans(d, i) {
var height = sum(cellSize.slice(0, i));
return "translate(0, " + (y + height + i * shapePadding) + ")";
};
textTrans = function textTrans(d, i) {
return "translate( " + (maxW + labelOffset) + ",\n " + (shapeSize[i].y + shapeSize[i].height / 2 + 5) + ")";
};
})();
} else if (orient === "horizontal") {
(function () {
cellTrans = function cellTrans(d, i) {
var width = sum(shapeSize.slice(0, i), function (d) {
return d.width;
});
var y = shape == "circle" || shape == "line" ? maxH / 2 : 0;
return "translate(" + (width + i * shapePadding) + ", " + y + ")";
};
var offset = shape == "line" ? maxH / 2 : maxH;
textTrans = function textTrans(d, i) {
return "translate( " + (shapeSize[i].width * textAlign + shapeSize[i].x) + ",\n " + (offset + labelOffset) + ")";
};
})();
}
helper.d3_placement(orient, cell, cellTrans, text, textTrans, labelAlign);
helper.d3_title(svg, title, classPrefix, titleWidth);
cell.transition().style("opacity", 1);
}
legend.scale = function (_) {
if (!arguments.length) return scale;
scale = _;
return legend;
};
legend.cells = function (_) {
if (!arguments.length) return cells;
if (_.length > 1 || _ >= 2) {
cells = _;
}
return legend;
};
legend.cellFilter = function (_) {
if (!arguments.length) return cellFilter;
cellFilter = _;
return legend;
};
legend.shape = function (_, d) {
if (!arguments.length) return shape;
if (_ == "rect" || _ == "circle" || _ == "line") {
shape = _;
path = d;
}
return legend;
};
legend.shapeWidth = function (_) {
if (!arguments.length) return shapeWidth;
shapeWidth = +_;
return legend;
};
legend.shapePadding = function (_) {
if (!arguments.length) return shapePadding;
shapePadding = +_;
return legend;
};
legend.labels = function (_) {
if (!arguments.length) return labels;
labels = _;
return legend;
};
legend.labelAlign = function (_) {
if (!arguments.length) return labelAlign;
if (_ == "start" || _ == "end" || _ == "middle") {
labelAlign = _;
}
return legend;
};
legend.locale = function (_) {
if (!arguments.length) return locale;
locale = formatLocale(_);
return legend;
};
legend.labelFormat = function (_) {
if (!arguments.length) return legend.locale().format(specifier);
specifier = formatSpecifier(_);
return legend;
};
legend.labelOffset = function (_) {
if (!arguments.length) return labelOffset;
labelOffset = +_;
return legend;
};
legend.labelDelimiter = function (_) {
if (!arguments.length) return labelDelimiter;
labelDelimiter = _;
return legend;
};
legend.labelWrap = function (_) {
if (!arguments.length) return labelWrap;
labelWrap = _;
return legend;
};
legend.orient = function (_) {
if (!arguments.length) return orient;
_ = _.toLowerCase();
if (_ == "horizontal" || _ == "vertical") {
orient = _;
}
return legend;
};
legend.ascending = function (_) {
if (!arguments.length) return ascending;
ascending = !!_;
return legend;
};
legend.classPrefix = function (_) {
if (!arguments.length) return classPrefix;
classPrefix = _;
return legend;
};
legend.title = function (_) {
if (!arguments.length) return title;
title = _;
return legend;
};
legend.titleWidth = function (_) {
if (!arguments.length) return titleWidth;
titleWidth = _;
return legend;
};
legend.on = function () {
var value = legendDispatcher.on.apply(legendDispatcher, arguments);
return value === legendDispatcher ? legend : value;
};
return legend;
}
Example #18
Source File: indexRollupNext.js From t-viSNE with MIT License | 4 votes |
function color() {
var scale = scaleLinear(),
shape = "rect",
shapeWidth = 15,
shapeHeight = 15,
shapeRadius = 10,
shapePadding = 2,
cells = [5],
cellFilter = void 0,
labels = [],
classPrefix = "",
useClass = false,
title = "",
locale = helper.d3_defaultLocale,
specifier = helper.d3_defaultFormatSpecifier,
labelOffset = 10,
labelAlign = "middle",
labelDelimiter = helper.d3_defaultDelimiter,
labelWrap = void 0,
orient = "vertical",
ascending = false,
path = void 0,
titleWidth = void 0,
legendDispatcher = dispatch("cellover", "cellout", "cellclick");
function legend(svg) {
var type = helper.d3_calcType(scale, ascending, cells, labels, locale.format(specifier), labelDelimiter),
legendG = svg.selectAll("g").data([scale]);
legendG.enter().append("g").attr("class", classPrefix + "legendCells");
if (cellFilter) {
helper.d3_filterCells(type, cellFilter);
}
var cell = svg.select("." + classPrefix + "legendCells").selectAll("." + classPrefix + "cell").data(type.data);
var cellEnter = cell.enter().append("g").attr("class", classPrefix + "cell");
cellEnter.append(shape).attr("class", classPrefix + "swatch");
var shapes = svg.selectAll("g." + classPrefix + "cell " + shape + "." + classPrefix + "swatch").data(type.data);
//add event handlers
helper.d3_addEvents(cellEnter, legendDispatcher);
cell.exit().transition().style("opacity", 0).remove();
shapes.exit().transition().style("opacity", 0).remove();
shapes = shapes.merge(shapes);
helper.d3_drawShapes(shape, shapes, shapeHeight, shapeWidth, shapeRadius, path);
var text = helper.d3_addText(svg, cellEnter, type.labels, classPrefix, labelWrap);
// we need to merge the selection, otherwise changes in the legend (e.g. change of orientation) are applied only to the new cells and not the existing ones.
cell = cellEnter.merge(cell);
// sets placement
var textSize = text.nodes().map(function (d) {
return d.getBBox();
}),
shapeSize = shapes.nodes().map(function (d) {
return d.getBBox();
});
//sets scale
//everything is fill except for line which is stroke,
if (!useClass) {
if (shape == "line") {
shapes.style("stroke", type.feature);
} else {
shapes.style("fill", type.feature);
}
} else {
shapes.attr("class", function (d) {
return classPrefix + "swatch " + type.feature(d);
});
}
var cellTrans = void 0,
textTrans = void 0,
textAlign = labelAlign == "start" ? 0 : labelAlign == "middle" ? 0.5 : 1;
//positions cells and text
if (orient === "vertical") {
(function () {
var cellSize = textSize.map(function (d, i) {
return Math.max(d.height, shapeSize[i].height);
});
cellTrans = function cellTrans(d, i) {
var height = sum(cellSize.slice(0, i));
return "translate(0, " + (height + i * shapePadding) + ")";
};
textTrans = function textTrans(d, i) {
return "translate( " + (shapeSize[i].width + shapeSize[i].x + labelOffset) + ", " + (shapeSize[i].y + shapeSize[i].height / 2 + 5) + ")";
};
})();
} else if (orient === "horizontal") {
cellTrans = function cellTrans(d, i) {
return "translate(" + i * (shapeSize[i].width + shapePadding) + ",0)";
};
textTrans = function textTrans(d, i) {
return "translate(" + (shapeSize[i].width * textAlign + shapeSize[i].x) + ",\n " + (shapeSize[i].height + shapeSize[i].y + labelOffset + 8) + ")";
};
}
helper.d3_placement(orient, cell, cellTrans, text, textTrans, labelAlign);
helper.d3_title(svg, title, classPrefix, titleWidth);
cell.transition().style("opacity", 1);
}
legend.scale = function (_) {
if (!arguments.length) return scale;
scale = _;
return legend;
};
legend.cells = function (_) {
if (!arguments.length) return cells;
if (_.length > 1 || _ >= 2) {
cells = _;
}
return legend;
};
legend.cellFilter = function (_) {
if (!arguments.length) return cellFilter;
cellFilter = _;
return legend;
};
legend.shape = function (_, d) {
if (!arguments.length) return shape;
if (_ == "rect" || _ == "circle" || _ == "line" || _ == "path" && typeof d === "string") {
shape = _;
path = d;
}
return legend;
};
legend.shapeWidth = function (_) {
if (!arguments.length) return shapeWidth;
shapeWidth = +_;
return legend;
};
legend.shapeHeight = function (_) {
if (!arguments.length) return shapeHeight;
shapeHeight = +_;
return legend;
};
legend.shapeRadius = function (_) {
if (!arguments.length) return shapeRadius;
shapeRadius = +_;
return legend;
};
legend.shapePadding = function (_) {
if (!arguments.length) return shapePadding;
shapePadding = +_;
return legend;
};
legend.labels = function (_) {
if (!arguments.length) return labels;
labels = _;
return legend;
};
legend.labelAlign = function (_) {
if (!arguments.length) return labelAlign;
if (_ == "start" || _ == "end" || _ == "middle") {
labelAlign = _;
}
return legend;
};
legend.locale = function (_) {
if (!arguments.length) return locale;
locale = formatLocale(_);
return legend;
};
legend.labelFormat = function (_) {
if (!arguments.length) return legend.locale().format(specifier);
specifier = formatSpecifier(_);
return legend;
};
legend.labelOffset = function (_) {
if (!arguments.length) return labelOffset;
labelOffset = +_;
return legend;
};
legend.labelDelimiter = function (_) {
if (!arguments.length) return labelDelimiter;
labelDelimiter = _;
return legend;
};
legend.labelWrap = function (_) {
if (!arguments.length) return labelWrap;
labelWrap = _;
return legend;
};
legend.useClass = function (_) {
if (!arguments.length) return useClass;
if (_ === true || _ === false) {
useClass = _;
}
return legend;
};
legend.orient = function (_) {
if (!arguments.length) return orient;
_ = _.toLowerCase();
if (_ == "horizontal" || _ == "vertical") {
orient = _;
}
return legend;
};
legend.ascending = function (_) {
if (!arguments.length) return ascending;
ascending = !!_;
return legend;
};
legend.classPrefix = function (_) {
if (!arguments.length) return classPrefix;
classPrefix = _;
return legend;
};
legend.title = function (_) {
if (!arguments.length) return title;
title = _;
return legend;
};
legend.titleWidth = function (_) {
if (!arguments.length) return titleWidth;
titleWidth = _;
return legend;
};
legend.textWrap = function (_) {
if (!arguments.length) return textWrap;
textWrap = _;
return legend;
};
legend.on = function () {
var value = legendDispatcher.on.apply(legendDispatcher, arguments);
return value === legendDispatcher ? legend : value;
};
return legend;
}
Example #19
Source File: density.js From cs-wiki with GNU General Public License v3.0 | 4 votes |
export default function() {
var x = defaultX,
y = defaultY,
weight = defaultWeight,
dx = 960,
dy = 500,
r = 20, // blur radius
k = 2, // log2(grid cell size)
o = r * 3, // grid offset, to pad for blur
n = (dx + o * 2) >> k, // grid width
m = (dy + o * 2) >> k, // grid height
threshold = constant(20);
function density(data) {
var values0 = new Float32Array(n * m),
values1 = new Float32Array(n * m);
data.forEach(function(d, i, data) {
var xi = (+x(d, i, data) + o) >> k,
yi = (+y(d, i, data) + o) >> k,
wi = +weight(d, i, data);
if (xi >= 0 && xi < n && yi >= 0 && yi < m) {
values0[xi + yi * n] += wi;
}
});
// TODO Optimize.
blurX({width: n, height: m, data: values0}, {width: n, height: m, data: values1}, r >> k);
blurY({width: n, height: m, data: values1}, {width: n, height: m, data: values0}, r >> k);
blurX({width: n, height: m, data: values0}, {width: n, height: m, data: values1}, r >> k);
blurY({width: n, height: m, data: values1}, {width: n, height: m, data: values0}, r >> k);
blurX({width: n, height: m, data: values0}, {width: n, height: m, data: values1}, r >> k);
blurY({width: n, height: m, data: values1}, {width: n, height: m, data: values0}, r >> k);
var tz = threshold(values0);
// Convert number of thresholds into uniform thresholds.
if (!Array.isArray(tz)) {
var stop = max(values0);
tz = tickStep(0, stop, tz);
tz = range(0, Math.floor(stop / tz) * tz, tz);
tz.shift();
}
return contours()
.thresholds(tz)
.size([n, m])
(values0)
.map(transform);
}
function transform(geometry) {
geometry.value *= Math.pow(2, -2 * k); // Density in points per square pixel.
geometry.coordinates.forEach(transformPolygon);
return geometry;
}
function transformPolygon(coordinates) {
coordinates.forEach(transformRing);
}
function transformRing(coordinates) {
coordinates.forEach(transformPoint);
}
// TODO Optimize.
function transformPoint(coordinates) {
coordinates[0] = coordinates[0] * Math.pow(2, k) - o;
coordinates[1] = coordinates[1] * Math.pow(2, k) - o;
}
function resize() {
o = r * 3;
n = (dx + o * 2) >> k;
m = (dy + o * 2) >> k;
return density;
}
density.x = function(_) {
return arguments.length ? (x = typeof _ === "function" ? _ : constant(+_), density) : x;
};
density.y = function(_) {
return arguments.length ? (y = typeof _ === "function" ? _ : constant(+_), density) : y;
};
density.weight = function(_) {
return arguments.length ? (weight = typeof _ === "function" ? _ : constant(+_), density) : weight;
};
density.size = function(_) {
if (!arguments.length) return [dx, dy];
var _0 = Math.ceil(_[0]), _1 = Math.ceil(_[1]);
if (!(_0 >= 0) && !(_0 >= 0)) throw new Error("invalid size");
return dx = _0, dy = _1, resize();
};
density.cellSize = function(_) {
if (!arguments.length) return 1 << k;
if (!((_ = +_) >= 1)) throw new Error("invalid cell size");
return k = Math.floor(Math.log(_) / Math.LN2), resize();
};
density.thresholds = function(_) {
return arguments.length ? (threshold = typeof _ === "function" ? _ : Array.isArray(_) ? constant(slice.call(_)) : constant(_), density) : threshold;
};
density.bandwidth = function(_) {
if (!arguments.length) return Math.sqrt(r * (r + 1));
if (!((_ = +_) >= 0)) throw new Error("invalid bandwidth");
return r = Math.round((Math.sqrt(4 * _ * _ + 1) - 1) / 2), resize();
};
return density;
}
Example #20
Source File: density.js From cs-wiki with GNU General Public License v3.0 | 4 votes |
export default function() {
var x = defaultX,
y = defaultY,
weight = defaultWeight,
dx = 960,
dy = 500,
r = 20, // blur radius
k = 2, // log2(grid cell size)
o = r * 3, // grid offset, to pad for blur
n = (dx + o * 2) >> k, // grid width
m = (dy + o * 2) >> k, // grid height
threshold = constant(20);
function density(data) {
var values0 = new Float32Array(n * m),
values1 = new Float32Array(n * m),
pow2k = Math.pow(2, -k);
data.forEach(function(d, i, data) {
var xi = (x(d, i, data) + o) * pow2k,
yi = (y(d, i, data) + o) * pow2k,
wi = +weight(d, i, data);
if (xi >= 0 && xi < n && yi >= 0 && yi < m) {
var x0 = Math.floor(xi),
y0 = Math.floor(yi),
xt = xi - x0 - 0.5,
yt = yi - y0 - 0.5;
values0[x0 + y0 * n] += (1 - xt) * (1 - yt) * wi;
values0[x0 + 1 + y0 * n] += xt * (1 - yt) * wi;
values0[x0 + 1 + (y0 + 1) * n] += xt * yt * wi;
values0[x0 + (y0 + 1) * n] += (1 - xt) * yt * wi;
}
});
// TODO Optimize.
blurX({width: n, height: m, data: values0}, {width: n, height: m, data: values1}, r >> k);
blurY({width: n, height: m, data: values1}, {width: n, height: m, data: values0}, r >> k);
blurX({width: n, height: m, data: values0}, {width: n, height: m, data: values1}, r >> k);
blurY({width: n, height: m, data: values1}, {width: n, height: m, data: values0}, r >> k);
blurX({width: n, height: m, data: values0}, {width: n, height: m, data: values1}, r >> k);
blurY({width: n, height: m, data: values1}, {width: n, height: m, data: values0}, r >> k);
var tz = threshold(values0);
// Convert number of thresholds into uniform thresholds.
if (!Array.isArray(tz)) {
var stop = max(values0);
tz = tickStep(0, stop, tz);
tz = range(0, Math.floor(stop / tz) * tz, tz);
tz.shift();
}
return contours()
.thresholds(tz)
.size([n, m])
(values0)
.map(transform);
}
function transform(geometry) {
geometry.value *= Math.pow(2, -2 * k); // Density in points per square pixel.
geometry.coordinates.forEach(transformPolygon);
return geometry;
}
function transformPolygon(coordinates) {
coordinates.forEach(transformRing);
}
function transformRing(coordinates) {
coordinates.forEach(transformPoint);
}
// TODO Optimize.
function transformPoint(coordinates) {
coordinates[0] = coordinates[0] * Math.pow(2, k) - o;
coordinates[1] = coordinates[1] * Math.pow(2, k) - o;
}
function resize() {
o = r * 3;
n = (dx + o * 2) >> k;
m = (dy + o * 2) >> k;
return density;
}
density.x = function(_) {
return arguments.length ? (x = typeof _ === "function" ? _ : constant(+_), density) : x;
};
density.y = function(_) {
return arguments.length ? (y = typeof _ === "function" ? _ : constant(+_), density) : y;
};
density.weight = function(_) {
return arguments.length ? (weight = typeof _ === "function" ? _ : constant(+_), density) : weight;
};
density.size = function(_) {
if (!arguments.length) return [dx, dy];
var _0 = +_[0], _1 = +_[1];
if (!(_0 >= 0 && _1 >= 0)) throw new Error("invalid size");
return dx = _0, dy = _1, resize();
};
density.cellSize = function(_) {
if (!arguments.length) return 1 << k;
if (!((_ = +_) >= 1)) throw new Error("invalid cell size");
return k = Math.floor(Math.log(_) / Math.LN2), resize();
};
density.thresholds = function(_) {
return arguments.length ? (threshold = typeof _ === "function" ? _ : Array.isArray(_) ? constant(slice.call(_)) : constant(_), density) : threshold;
};
density.bandwidth = function(_) {
if (!arguments.length) return Math.sqrt(r * (r + 1));
if (!((_ = +_) >= 0)) throw new Error("invalid bandwidth");
return r = Math.round((Math.sqrt(4 * _ * _ + 1) - 1) / 2), resize();
};
return density;
}
Example #21
Source File: county-chart.js From website with Apache License 2.0 | 4 votes |
CountyChart = ({ data, field, label, increments }) => {
const height = 400
const width = 400
const labelOffset = 150
const heightOffset = 50
const yScale = scaleBand()
.domain(data.map((d, index) => index))
.range([0, height])
const xScale = scaleLinear()
.domain([0, max(data, d => d[field])])
.nice()
.range([0, width])
const verticalLines = []
for (let i = 0; i < max(data, d => d[field]); i += increments) {
verticalLines.push(i)
}
return (
<svg
className={countyChartStyles.chart}
viewBox={`0 0 ${width + labelOffset} ${height + heightOffset}`}
>
<g
transform={`translate(${labelOffset} ${heightOffset})`}
height={height - heightOffset}
width={width - labelOffset}
>
{data.map((d, index) => (
<rect
key={`${field}-${d.name}-${d.state}`}
x={0}
y={yScale(index)}
height={10}
width={xScale(d[field])}
className={`${countyChartStyles.bar} ${
countyChartStyles[groupClasses[d.demographics.largestRace1]]
}`}
fill="#000"
/>
))}
</g>
{verticalLines.map(tick => (
<g key={`${field}-${tick}`}>
<svg y={20} x={xScale(tick) + labelOffset} width={1}>
<line
x1="0"
y1="0"
x2="0"
y2={height + heightOffset}
className={countyChartStyles.verticalTick}
style={{ height }}
/>
</svg>
</g>
))}
<g transform="translate(0, 15)">
<svg
y={0}
x={labelOffset}
width={labelOffset}
className={countyChartStyles.tick}
>
<text className={countyChartStyles.label}>{label}</text>
</svg>
{xScale.ticks(3).map(
(tick, i) =>
i < 3 && (
<g key={`${field}-${tick}`}>
<svg
y={20}
x={xScale(tick) + labelOffset}
width={labelOffset}
className={countyChartStyles.tick}
>
<text
className={classnames(
countyChartStyles.label,
countyChartStyles.centered,
)}
>
{tick.toLocaleString()}
</text>
</svg>
</g>
),
)}
</g>
{data.map((d, index) => (
<g
key={`${d.field}-${d.county}`}
transform={`translate(0, ${heightOffset})`}
>
<svg
y={yScale(index) + 10}
x={labelOffset - 10}
width={labelOffset}
className={countyChartStyles.countyLabel}
>
<text className={countyChartStyles.label}>
{d.name}, {d.demographics.abbreviation}
</text>
</svg>
</g>
))}
</svg>
)
}
Example #22
Source File: multi-line-chart.js From website with Apache License 2.0 | 4 votes |
MultiLineChart = ({
data,
marginBottom,
marginLeft,
marginRight,
marginTop = 0,
showTicks,
width,
height,
yMax,
yTicks,
lastXTick,
perCapLabel,
}) => {
const totalXMargin = marginLeft + marginRight
const totalYMargin = marginTop + marginBottom
const dates = []
const values = []
data.forEach(item => {
Object.keys(item.data).forEach(category => {
item.data[category].forEach(row => {
dates.push(row.date)
values.push(row.value)
})
})
})
const dateDomain = extent(dates)
const xScaleTime = scaleTime()
.domain(dateDomain)
.range([marginLeft, width - marginRight])
const yMaxEffective = yMax || max(values)
const yScale = scaleLinear()
.domain([0, yMaxEffective])
.nice()
.range([height - totalYMargin, 0])
const msInOneMonth = 2628000000
const monthlyTickInterval = Math.ceil(
Math.abs((dateDomain[1] - dateDomain[0]) / (msInOneMonth * 6)),
)
const xTickAmount = timeMonth.every(monthlyTickInterval)
const yTicksThreshold = 4
const yTicksEffective =
yTicks || yMaxEffective < yTicksThreshold ? yMaxEffective : yTicksThreshold
const lastTime = xScaleTime.ticks(timeDay.every(1)).pop()
const lineFn = line()
.defined(d => !Number.isNaN(d.value) && d.value !== null)
.curve(curveCardinal)
.x(d => xScaleTime(d.date))
.y(d => yScale(d.value))
// TODO make this dry-er w/r/t the single line chart component
return (
<>
<svg
className={chartStyles.chart}
viewBox={`0 0 ${width} ${height}`}
aria-hidden
>
{/* y ticks */}
<g transform={`translate(${marginLeft} ${marginTop})`}>
{yScale.ticks(yTicksEffective).map(
(tick, i) =>
i < showTicks && (
<g key={tick}>
{/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
<svg
y={yScale(tick) + 6}
x="-10"
className={chartStyles.yTickLabel}
>
<text className={chartStyles.label}>
{formatNumber(tick)}
{tick > 0 &&
perCapLabel /* this only displays if passed */}
</text>
</svg>
<line
className={chartStyles.gridLine}
x1={0}
x2={width - totalXMargin}
y1={yScale(tick)}
y2={yScale(tick)}
/>
</g>
),
)}
</g>
{/* x ticks (dates) */}
<g transform={`translate(0, ${height - marginBottom})`}>
{xScaleTime.ticks(xTickAmount).map(d => (
<Fragment key={`x-${d}`}>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
key={d}
x={xScaleTime(d)}
y="20"
>{`${formatDate(d)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(d)}
y1="0"
x2={xScaleTime(d)}
y2="5"
/>
</Fragment>
))}
{lastXTick && (
<>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
x={xScaleTime(lastTime)}
y="20"
>{`${formatDate(lastTime)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(lastTime)}
y1="0"
x2={xScaleTime(lastTime)}
y2="5"
/>
</>
)}
</g>
<mask id="dataMask">
<rect
x="0"
y="0"
width={width - marginRight}
height={height - totalYMargin}
fill="white"
/>
</mask>
{/* data */}
<g transform={`translate(0 ${marginTop})`} mask="url(#dataMask)">
{data && (
<>
{data.map(item => (
<>
{Object.keys(item.data).map(category => (
<path
d={lineFn(item.data[category])}
stroke={item.colorMap[category]}
strokeWidth="1px"
fill="none"
/>
))}
</>
))}
</>
)}
</g>
</svg>
</>
)
}
Example #23
Source File: line-chart.js From website with Apache License 2.0 | 4 votes |
LineChart = ({
data,
marginBottom,
marginLeft,
marginRight,
marginTop = 0,
showTicks,
width,
height,
yMax,
yTicks,
lastXTick,
perCapLabel,
renderTooltipContents,
}) => {
const totalXMargin = marginLeft + marginRight
const totalYMargin = marginTop + marginBottom
const dates = []
const values = []
data.forEach(item => {
item.data.forEach(row => {
dates.push(row.date)
values.push(row.value)
})
})
const [tooltip, setTooltip] = useState(null)
const [timeoutRef, setTimeoutRef] = useState(null)
const hover = (event, dataLine) => {
// Ensure that tooltip doesn't flash when transitioning between bars
if (timeoutRef) {
clearTimeout(timeoutRef)
}
const isTouchEvent = !event.clientX
const eventX = isTouchEvent ? event.touches[0].clientX : event.clientX
const eventY = isTouchEvent ? event.touches[0].clientY : event.clientY
setTooltip({
top: isTouchEvent ? eventY - 130 : eventY + 10,
left: isTouchEvent ? eventX - 80 : eventX + 5,
d: dataLine,
})
}
const mouseOut = () => {
if (timeoutRef) {
clearTimeout(timeoutRef)
}
setTimeoutRef(setTimeout(() => setTooltip(null), 200))
}
const dateDomain = extent(dates)
const xScaleTime = scaleTime()
.domain(dateDomain)
.range([marginLeft, width - marginRight])
const yMaxEffective = yMax || max(values)
const yScale = scaleLinear()
.domain([0, yMaxEffective])
.nice()
.range([height - totalYMargin, 0])
const msInOneMonth = 2628000000
const monthlyTickInterval = Math.ceil(
Math.abs((dateDomain[1] - dateDomain[0]) / (msInOneMonth * 6)),
)
const xTickAmount = timeMonth.every(monthlyTickInterval)
const yTicksThreshold = 4
const yTicksEffective =
yTicks || yMaxEffective < yTicksThreshold ? yMaxEffective : yTicksThreshold
const lastTime = xScaleTime.ticks(timeDay.every(1)).pop()
const lineFn = line()
.defined(d => !Number.isNaN(d.value) && d.value !== null)
.curve(curveCardinal)
.x(d => xScaleTime(d.date))
.y(d => yScale(d.value))
return (
<>
<svg
className={chartStyles.chart}
viewBox={`0 0 ${width} ${height}`}
aria-hidden
>
{/* y ticks */}
<g transform={`translate(${marginLeft} ${marginTop})`}>
{yScale.ticks(yTicksEffective).map(
(tick, i) =>
i < showTicks && (
<g key={tick}>
{/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
<svg
y={yScale(tick) + 6}
x="-10"
className={chartStyles.yTickLabel}
>
<text className={chartStyles.label}>
{formatNumber(tick)}
{tick > 0 &&
perCapLabel /* this only displays if passed */}
</text>
</svg>
<line
className={chartStyles.gridLine}
x1={0}
x2={width - totalXMargin}
y1={yScale(tick)}
y2={yScale(tick)}
/>
</g>
),
)}
</g>
{/* x ticks (dates) */}
<g transform={`translate(0, ${height - marginBottom})`}>
{xScaleTime.ticks(xTickAmount).map(d => (
<Fragment key={`x-${d}`}>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
key={d}
x={xScaleTime(d)}
y="20"
>{`${formatDate(d)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(d)}
y1="0"
x2={xScaleTime(d)}
y2="5"
/>
</Fragment>
))}
{lastXTick && (
<>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
x={xScaleTime(lastTime)}
y="20"
>{`${formatDate(lastTime)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(lastTime)}
y1="0"
x2={xScaleTime(lastTime)}
y2="5"
/>
</>
)}
</g>
<mask id="dataMask">
<rect
x="0"
y="0"
width={width - marginRight}
height={height - totalYMargin}
fill="white"
/>
</mask>
{/* data */}
<g transform={`translate(0 ${marginTop})`} mask="url(#dataMask)">
{data && (
<>
{data.map(dataLine => (
<>
<path
d={lineFn(dataLine.data)}
stroke={dataLine.color}
strokeWidth={dataLine.stroke}
fill="none"
/>
{/* Add a wider hidden path for tooltips. */}
<path
d={lineFn(dataLine.data)}
stroke="transparent"
strokeWidth={6}
fill="none"
onMouseOver={event => hover(event, dataLine)}
onFocus={event => hover(event, dataLine)}
onMouseOut={mouseOut}
onBlur={mouseOut}
/>
</>
))}
</>
)}
</g>
</svg>
{renderTooltipContents && tooltip && (
<Tooltip {...tooltip}>{renderTooltipContents(tooltip.d)} </Tooltip>
)}
</>
)
}
Example #24
Source File: bar-chart.js From website with Apache License 2.0 | 4 votes |
BarChart = ({
data,
lineData,
refLineData,
annotations,
handleAnnotationClick,
fill,
lineColor,
marginBottom,
marginLeft,
marginRight,
marginTop,
showTicks,
width,
height,
yMax,
yTicks,
lastXTick,
renderTooltipContents,
perCapLabel,
}) => {
const chartRef = useRef()
const [tooltip, setTooltip] = useState(null)
// Used for tooltip optimization
const [timeoutRef, setTimeoutRef] = useState(null)
const [keyboardFocus, setKeyboardFocus] = useState(false)
// Used when placing annotations
const getValueForDate = date => {
const dateData = data.find(d => d.date.getTime() === date.getTime())
return dateData && dateData.value
}
const totalXMargin = marginLeft + marginRight
const totalYMargin = marginTop + marginBottom
// The x range is over the area in which the chart should be displaying.
// We don't use an X transform to place it in the correct spot, we use range
// instead
const xScale = scaleBand()
.domain(data.map(d => d.date))
.range([marginLeft, width - marginRight])
.padding(0.1)
const dateDomain = extent(data, d => d.date)
// Should probably refactor to use a single x-axis scale
// but the bars make use of the band.
const xScaleTime = scaleTime()
.domain(dateDomain)
.range([marginLeft, width - marginRight])
const yMaxEffective =
yMax || max([...data, ...(refLineData || [])], d => d.value)
const yScale = scaleLinear()
.domain([0, yMaxEffective])
.nice()
.range([height - totalYMargin, 0])
const msInOneMonth = 2628000000
const monthlyTickInterval = Math.ceil(
Math.abs((dateDomain[1] - dateDomain[0]) / (msInOneMonth * 6)),
)
const xTickAmount = timeMonth.every(monthlyTickInterval)
const yTicksThreshold = 4
const yTicksEffective =
yTicks || yMaxEffective < yTicksThreshold ? yMaxEffective : yTicksThreshold
const lastTime = xScaleTime.ticks(timeDay.every(1)).pop()
let lineFn = null
if (lineData) {
lineFn = line()
.defined(d => !Number.isNaN(d.value) && d.value !== null)
.curve(curveCardinal)
.x(d => xScaleTime(d.date))
.y(d => yScale(d.value))
}
const hover = (event, d) => {
// Ensure that tooltip doesn't flash when transitioning between bars
if (timeoutRef) {
clearTimeout(timeoutRef)
}
const isTouchEvent = !event.clientX
const eventX = isTouchEvent ? event.touches[0].clientX : event.clientX
const eventY = isTouchEvent ? event.touches[0].clientY : event.clientY
setTooltip({
top: isTouchEvent ? eventY - 130 : eventY + 10,
left: isTouchEvent ? eventX - 80 : eventX + 5,
d,
})
}
const mouseOut = () => {
if (timeoutRef) {
clearTimeout(timeoutRef)
}
setTimeoutRef(setTimeout(() => setTooltip(null), 200))
}
useEffect(() => {
if (keyboardFocus === false || typeof data[keyboardFocus] === 'undefined') {
return
}
const column = data[keyboardFocus]
setTooltip({
top: chartRef.current.getBoundingClientRect().top,
left: chartRef.current.getBoundingClientRect().left,
d: column,
})
}, [keyboardFocus])
return (
<>
<svg
className={classnames(chartStyles.chart, styles.chart)}
viewBox={`0 0 ${width} ${height}`}
tabIndex="0"
aria-hidden
ref={chartRef}
onBlur={() => {
setTooltip(null)
setKeyboardFocus(false)
}}
onKeyDown={event => {
if (event.key === 'Escape') {
setTooltip(null)
setKeyboardFocus(false)
chartRef.current.blur()
}
if (event.key === 'ArrowRight') {
setKeyboardFocus(
keyboardFocus < data.length ? keyboardFocus + 1 : data.length,
)
}
if (
(event.shiftKey && event.key === 'Tab') ||
event.key === 'ArrowLeft'
) {
setKeyboardFocus(keyboardFocus > 0 ? keyboardFocus - 1 : 0)
}
}}
>
<g transform={`translate(${marginLeft} ${marginTop})`}>
<text className={classnames(chartStyles.label, styles.directions)}>
Use arrows to move, Escape to leave.
</text>
</g>
{/* y ticks */}
<g transform={`translate(${marginLeft} ${marginTop})`}>
{yScale.ticks(yTicksEffective).map(
(tick, i) =>
i < showTicks && (
<g key={tick}>
{/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
<svg
y={yScale(tick) + 6}
x="-10"
className={chartStyles.yTickLabel}
>
<text className={chartStyles.label}>
{formatNumber(tick)}
{tick > 0 &&
perCapLabel /* this only displays if passed */}
</text>
</svg>
<line
className={chartStyles.gridLine}
x1={0}
x2={width - totalXMargin}
y1={yScale(tick)}
y2={yScale(tick)}
/>
</g>
),
)}
</g>
{/* x ticks (dates) */}
<g transform={`translate(0, ${height - marginBottom})`}>
{xScaleTime.ticks(xTickAmount).map(d => (
<Fragment key={`x-${d}`}>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
key={d}
x={xScaleTime(d)}
y="20"
>{`${formatDate(d)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(d)}
y1="0"
x2={xScaleTime(d)}
y2="5"
/>
</Fragment>
))}
{lastXTick && (
<>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
x={xScaleTime(lastTime)}
y="20"
>{`${formatDate(lastTime)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(lastTime)}
y1="0"
x2={xScaleTime(lastTime)}
y2="5"
/>
</>
)}
</g>
<mask id="dataMask">
<rect
x="0"
y="0"
width={width - marginRight}
height={height - totalYMargin}
fill="white"
/>
</mask>
{/* data */}
<g transform={`translate(0 ${marginTop})`} mask="url(#dataMask)">
{/* bars (data) */}
{data.map((d, key) => (
<rect
key={d.date + d.value}
x={xScale(d.date)}
y={yScale(d.value)}
height={yScale(0) - yScale(d.value)}
width={xScale.bandwidth()}
fillOpacity={lineData ? 1 : 0.8}
fill={fill}
className={classnames(
renderTooltipContents && styles.interactiveBar,
key === keyboardFocus && styles.selected,
)}
onMouseOver={event => hover(event, d)}
onFocus={event => hover(event, d)}
onMouseOut={mouseOut}
onBlur={mouseOut}
/>
))}
{/* line */}
{lineData && (
<path
d={lineFn(lineData)}
stroke={lineColor}
strokeWidth="3"
fill="none"
/>
)}
{/* reference line */}
{refLineData && (
<path
d={lineFn(refLineData)}
stroke="black"
strokeWidth="2"
strokeDasharray="4"
fill="none"
/>
)}
</g>
{/* annotations */}
{annotations && (
<g transform={`translate(0 ${marginTop})`}>
{annotations
.filter(
annotation =>
xScaleTime(annotation.date) >= xScaleTime(dateDomain[0]) &&
xScaleTime(annotation.date) <= xScaleTime(dateDomain[1]),
)
.map(d => (
<AnnotationBubble
content={d}
xScaleTime={xScaleTime}
yScale={yScale}
handleAnnotationClick={handleAnnotationClick}
getValueForDate={getValueForDate}
/>
))}
</g>
)}
</svg>
{renderTooltipContents && tooltip && (
<Tooltip {...tooltip}>{renderTooltipContents(tooltip.d)} </Tooltip>
)}
</>
)
}