Published
Edited
Apr 26, 2019
11 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
callbacks = () => {
// global
const t = d3.zoomIdentity.translate(0,0).scale(1);
containers.pointmap
.call(zoomer)
.on("dblclick.zoom", (d) => {
containers.pointmap.transition()
.duration(200)
.ease(d3.easeLinear)
.call(zoomer.transform, t);
})
.on("dblclick", (d) => {
containers.pointmap.transition()
.duration(200)
.ease(d3.easeLinear)
.call(zoomer.transform, t);
});
// reset view
d3.selectAll("#reset").on("click", () =>{
containers.canvas.transition()
.duration(100)
.ease(d3.easeLinear)
.call(zoomer.transform, t);
containers.pointmap.transition()
.duration(100)
.ease(d3.easeLinear)
.call(zoomer.transform, t);
});
// clear canvas
d3.selectAll("#clear").on("click", () =>{
containers.context.clearRect(0,0,extents.width,extents.height);
containers.pointctx.clearRect(0,0,extents.width,extents.height);
}, false);
// draw on canvas
d3.selectAll("#draw").on("click", () =>{
draw(t);
}, false);
d3.selectAll("#tipToggle").on("click", () => {
if (d3.select("#tipToggle").node().checked) {
containers.pointmap.on('mousemove', tooltipDelay(tooltipFinder,200));
} else {
containers.pointmap.on('mousemove', null);
}
});
}
Insert cell
Insert cell
function tooltipFinder() {
let cLocation = d3.mouse(this);
let currentZoom = d3.zoomTransform(containers.pointmap.node());
let sx = currentZoom.rescaleX(x);
let sy = currentZoom.rescaleY(y);
let k = currentZoom.k;
//setup an aray to push the points to when > 1 point matches... i.e. two line pts.
let tooltipData = [];
// get the "points" data
const elems = containers.pointbin.selectAll("custom.points");
// check the distance between current location and all points.
elems.each( (d) => {
let dx = sx(d.x) - cLocation[0];
let dy = sy(d.y) - cLocation[1];
// Check distance and return if cursor is too far from the point.
if ( Math.sqrt(dx**2 + dy**2) > Math.sqrt(d.marker.size**2 * (k>1?k*0.75:k)) ) return false;
// close enough, push tooltip data
tooltipData.push(d);
});
// check if we didnt' find anything
if (!tooltipData.length) {
//containers.pointctx.resetTransform();
containers.pointctx.setTransform(1,0,0,1,0,0);
containers.pointctx.clearRect(0,0,layoutOpts.width,layoutOpts.height);
return false;
}
if (tooltipData.length > 1) {
// filter to grab a single point from each
let checkIndex = tooltipData.map(d => d.name);
checkIndex = checkIndex.map( (v,i,a) => a.indexOf(v)===i );
tooltipData = tooltipData.filter((dat,ind) => checkIndex[ind]);
}
const pxDat = [parseInt(sx(tooltipData[0].x)),parseInt(sy(tooltipData[0].y))];
// start by just plotting the first found tooltip.
var dirStr = '';
dirStr += (pxDat[1] < extents.height/2) ? 's' : 'n';
dirStr += (pxDat[0] < extents.width/2) ? 'e' : 'w' ;
// set transform and check if it applied
containers.pointctx.setTransform(1,0,0,1,pxDat[0],pxDat[1]);
const currentTrs = containers.pointctx.getTransform();
if ((currentTrs.e !== pxDat[0] && currentTrs.f !== pxDat[1])) {
containers.pointctx.setTransform(1,0,0,1,0,0);
containers.pointctx.clearRect(0,0,layoutOpts.width,layoutOpts.height);
return false;
}
makeTooltip(tooltipData,containers.pointctx,dirStr);
}
Insert cell
Insert cell
containers = {
// PLOT VIEWPORT -> iris.ui.axesPanel<.lastChild>
const div = d3.select(DOM.element('div'))
.style("margin", "auto")
.attr("width", `${layoutOpts.width}px`)
.attr("height", `${layoutOpts.height}px`)
.attr("class", "container")
.style("display","block");
//SVG for axes
const svg = div.append("svg")
.attr("width", `${layoutOpts.width}px`)
.attr("height", `${layoutOpts.height}px`)
.attr("class", "svg-container")
.attr("xmlns", "http://www.w3.org/2000/svg")
.append("g")
.attr("transform", `translate(${layoutOpts.margin.l},${layoutOpts.margin.t})`);
// Add axes groups
const XX = svg.append("g")
.call(gX);
const YY = svg.append("g")
.call(gY);
// Add title label containers
const XLAB = svg.append("g")
.call(xlabeler);
const YLAB = svg.append("g")
.call(ylabeler);
//Canvas
const canvas = div.append("canvas")
.attr("width", layoutOpts.width)
.attr("height", layoutOpts.height)
.style("margin", `${layoutOpts.margin.t}px ${layoutOpts.margin.r}px ${layoutOpts.margin.b}px ${layoutOpts.margin.l}px`)
.attr("class", "canvas-container");
// Map
const pointmap = div.append("canvas")
.attr("width", layoutOpts.width)
.attr("height", layoutOpts.height)
.style("margin", `${layoutOpts.margin.t}px ${layoutOpts.margin.r}px ${layoutOpts.margin.b}px ${layoutOpts.margin.l}px`)
.attr("class", "pointmap-container")
.attr("tabindex", "0");
// Canvas Context
const context = canvas.node().getContext('2d');// bottom layer doesn't need transparent bg
//clip the context
context.rect(1,1,extents.width-7,extents.height-1);
context.clip();
const pointctx = pointmap.node().getContext('2d');
//clip the context
pointctx.rect(1,1,extents.width-7,extents.height-1);
pointctx.clip();
// Containers for holding formatted data
let databin = d3.select(document.createElement('custom'));
let pointbin = d3.select(document.createElement('custom'));
return {div:div,svg:svg,canvas:canvas,context:context, pointmap:pointmap, pointctx:pointctx, databin:databin,pointbin:pointbin,X:XX,Y:YY,ylab:YLAB,xlab:XLAB}
}
Insert cell
Insert cell
function draw(transform) {
const sX = transform.rescaleX(x);
const sY = transform.rescaleY(y);

// Labels
containers.xlab.data([{label:XLABEL}]).call(xlabeler);
containers.ylab.data([{label:YLABEL}]).call(ylabeler);
//console.log(dats)
containers.X.call(fx.Axes(theData,extents).X.scale(sX));
containers.Y.call(fx.Axes(theData,extents).Y.scale(sY));
// get transforms for clearing
let cT = containers.context.getTransform();
let pT = containers.pointctx.getTransform();
containers.context.clearRect(-cT.e,-cT.f,extents.width,extents.height);
containers.pointctx.clearRect(-pT.e,-pT.f,extents.width,extents.height);//
// draw the data bound to databin and pointbin
drawLines(sX,sY,transform.k,containers.context);
// drawing points last draws them on top of the lines
drawPoints(sX,sY,transform.k,containers.context);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
gX = g => g
.attr("transform", `translate(0,${extents.height})`)
.attr("class", "axes x-axis")
.call(xAxis)
Insert cell
gY = g => g
.attr("class", "axes y-axis")
.call(yAxis)
Insert cell
xlabeler = g => {
let xlab = g.selectAll("text")
.data([{label:XLABEL}]);
xlab.enter()
.append("text")
.attr("class", "x-label")
.attr("y", extents.height-5)
.attr("x",extents.height/13)
.attr("fill", "rgba(60,60,60,0.6)")
.attr("text-anchor","left")
.attr("alignment-baseline","baseline")
.merge(xlab)
.text(d => d.label);
xlab.exit().remove();
}
Insert cell
ylabeler = g => {
let ylab = g.selectAll("text")
.data([{label:YLABEL}]);
ylab.enter()
.append("text")
.attr("class", "y-label")
.attr("transform", `rotate(-90)`)
.attr("x", -extents.height*12/13)
.attr("dy", 20)
.attr("fill", "rgba(60,60,60,0.6)")
.attr("text-anchor","left")
.attr("alignment-baseline","baseline")
.merge(ylab)
.text(d => d.label);
ylab.exit().remove();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
styles = html`
<style>
.container {
width: ${layoutOpts.width}px;
height:${layoutOpts.height}px;
}
.axes text {
font-family: ${layoutOpts.font.family};
font-size: ${layoutOpts.font.size*0.9}pt;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.axes line {
stroke-opacity: 0.6;
stroke: rgb(60,60,60);
stroke-width: 2px;
shape-rendering: crispEdges;
}
.canvas-container, .svg-container, .pointmap-container {
position: absolute;
background-color: transparent;
}
.canvas-container {
z-index: 1;
}
.pointmap-container {
z-index: 2;
cursor: grab;
}
.pointmap-container:active {
cursor: grabbing;
}
.pointmap-container:focus {
outline: none;
}
.x-label, .y-label {
font-family: ${layoutOpts.font.family};
font-size: ${layoutOpts.font.size}pt;
}
</style>
`
Insert cell
fx = ({
diff:(arr) => {
let i = 0;
let result = [];
while (i < arr.length-1) {
result.push(arr[++i] - arr[i-1]);
}
return result
},
markerType: function(type){
type = type || 'circle';
switch (type.toLowerCase()) {
case "cross":
return d3.symbolCross;
case "diamond":
return d3.symbolDiamond;
case "square":
return d3.symbolSquare;
case "star":
return d3.symbolStar;
case "y":
return d3.symbolWye;
case "triangle":
return d3.symbolTriangle;
default:
// circle is default if type is not there
return d3.symbolCircle;
}
},
strokeType: function(type) {
switch (type.toLowerCase()) {
case "solid":
return [0]
case "dashed":
return [18,12]
break;
case "dotted":
return [2,8]
case "dashed-dotted":
return [18,10,1,8,1,10]
}
},
Scales: function(d,dims) {
// xdomain padded by 1.2% of range (0.6% on each side)
const xdomain = d3.extent(d3.merge(d.map( o => o.x )));
//uncomment for padding.map( (v,i,ar) => v + (!i ? -this.diff(ar)*0.006 : this.diff(ar)*0.006) );//padded
// ydomain padded by 10% of range (5% each side)
const ydomain = d3.extent(d3.merge(d.map( o => o.y )))
.map( (v,i,ar) => v + (!i ? -this.diff(ar)*0.05 : this.diff(ar)*0.05) );//padded
let xscale = d3.scaleLinear()
.domain(xdomain).nice()
.range([0,dims.width]);
let yscale = d3.scaleLinear()
.domain(ydomain).nice()
.range([dims.height,0]);
return {X: xscale, Y: yscale}
},
Axes:function(d,dims) {
const sc = this.Scales(d,dims);
let xax = d3.axisBottom(sc.X)
.ticks(9);
let yax = d3.axisLeft(sc.Y)
.ticks(5)
return {X: xax, Y: yax}
},
rgb2A: function(col,opc) {
let rgb = col.split(',').map( v => v.replace(/\D/g,'') );
return `rgba(${rgb.slice(0,3).concat(opc.toString()).join(',')})`
}
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more