Built with blockbuilder.org
forked from interwebjill's block: Combining Canvas + SVG for Retina
Zoom technique thanks to Andrew Reid: Combining Canvas + SVG for Retina and his thorough StackOverflow answer
forked from interwebjill's block: Canvas area charts zoom on click using d3.interpolateString
xxxxxxxxxx
<head>
<meta charset="utf-8">
<style>
/* to prevent browser overscrolling */
html {
overflow: hidden;
}
body {
margin: 0; /* or else canvas alignment is off */
box-sizing: border-box;
}
/* for retina display */
canvas {
width: 880px;
height: 450px;
}
.chart {
position: absolute;
}
#chart-canvas {
margin: 20px 0 0 60px;
z-index: 1;
}
#axis-svg {
z-index: 2;
}
.st0 {
fill: rgb(221, 110, 68);
}
.hit-area {
fill: transparent;
}
</style>
</head>
<body>
<canvas width="1760" height="900" id="chart-canvas" class="chart"></canvas>
<svg width="960" height="500" id="axis-svg" class="chart"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script><script>
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 60},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var canvas = document.querySelector("canvas"),
context = canvas.getContext("2d"),
canvasScale = canvas.width / width;
var buttonRadius = 15;
var zoomRect = g.append("rect")
.attr("width", width)
.attr("height", height)
.attr("fill", "none");
var zoomButtonContainer = g.append("g")
.attr("transform",`translate(${-buttonRadius}, ${-buttonRadius})`);
var zoomButton = zoomButtonContainer.append("svg");
var parseDate = d3.timeParse("%Y/%m/%d %H:%M");
var buttonDate = parseDate("2015/02/07 12:00");
var color = d3.scaleOrdinal()
.domain(["PVkW", "TBLkW"])
.range(["rgb(237, 215, 90)", "rgba(0,140,220, 0.5)"]);
var len = 0,
sources = [],
xExtent = [],
maxSources = height;
var x = d3.scaleTime()
.range([0, width]);
var x2 = d3.scaleTime() // for zooming
.range([0, width]);
var xSVG = d3.scaleTime()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var ySVG = d3.scaleLinear()
.range([height, 0]);
var xAxisSVG = d3.axisBottom(xSVG);
var yAxisSVG = d3.axisLeft(ySVG);
var xGroupSVG = g.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("class", "x-axis");
var yGroupSVG = g.append("g")
.attr("class", "y-axis");
var area = d3.area()
.x(function(d) { return x(d.date); })
.y0(y(0))
.y1(function(d) { return y(d.kW); })
.curve(d3.curveStep)
.context(context);
d3.csv("kW.csv", type, function(error, data) {
if (error) throw error;
sources = data.columns.slice(1).map(function(id) {
return {
id: id,
values: data.map(function(d) {
return {date: d.date, kW: d[id]};
})
};
});
len = sources.length;
xExtent = d3.extent(data, function(d) { return d.date; });
maxSources = d3.max(sources, function(c) {
return d3.max(c.values, function(v) { return v.kW; });
});
x.domain(xExtent);
x2.domain(x.domain());
xSVG.domain(xExtent);
y.domain([0, maxSources ]);
ySVG.domain([0, maxSources ]);
yGroupSVG.call(yAxisSVG);
xGroupSVG.call(xAxisSVG);
color.domain(sources.map(function(c) { return c.id; }));
drawCanvas();
createButton();
});
function drawCanvas() {
context.save();
context.scale(canvasScale, canvasScale);
context.clearRect(0, 0, width, height);
// clip path
context.beginPath()
context.rect(0, 0, width, height);
context.clip();
for (i=0; i<len; i++) {
context.beginPath();
area(sources[i].values);
context.fillStyle = color(sources[i].id);
context.fill();
}
context.restore();
}
function createButton() {
var buttonKW = 466.0;
var zoomDomain = [parseDate("2015/01/04 0:00"), parseDate("2015/03/09 0:00")]
zoomButton
.attr("x", `${xSVG(buttonDate)}`)
.attr("y", `${ySVG(buttonKW)}`)
.attr("class", "zoom-button")
.html(
`
<circle class="st0" cx=${buttonRadius} cy=${buttonRadius} r="12" />
<line class="st1" x1=${buttonRadius} y1="10" x2=${buttonRadius} y2="12" />
<line class="st1" x1=${buttonRadius} y1="20" x2=${buttonRadius} y2="18" />
<line class="st1" x1="9" y1=${buttonRadius} x2="12" y2=${buttonRadius} />
<line class="st1" x1="20" y1=${buttonRadius} x2="18" y2=${buttonRadius} />
<circle class="hit-area" cx=${buttonRadius} cy=${buttonRadius} r=${buttonRadius} />
`
)
.on("mousedown", () => {
var k = d3.zoomTransform(zoomRect.node()).k;
if (k < 10) {
zoomToExtent(zoomDomain[0], zoomDomain[1]);
} else {
zoomOut();
}
});
}
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);
zoomRect
.attr("pointer-events", "all")
.call(zoom)
.on("dblclick.zoom", null);
function zoomToExtent(d0, d1) {
var startScale = d3.zoomTransform(zoomRect.node()).k;
var startTranslate = d3.zoomTransform(zoomRect.node()).x;
var endTranslate = -xSVG(d0);
var endScale = width / (xSVG(d1) - xSVG(d0));
zoomRect.call(zoom).transition()
.duration(1500)
.tween("transform", function() {
var interpolateScale = d3.interpolateNumber(startScale,endScale);
var interpolateTranslate = d3.interpolateNumber(startTranslate,endTranslate);
return function(t) {
console.log(t);
console.log(d3.zoomIdentity);
var t = d3.zoomIdentity.translate(interpolateTranslate(t),0).scale(interpolateScale(t));
console.log(t);
zoomed(t);
}
})
.on("end", function() {
d3.select(this).call(zoom.transform, d3.zoomIdentity
.translate(endTranslate, 0)
.scale(endScale));
})
}
function zoomOut() {
var startScale = d3.zoomTransform(zoomRect.node()).k;
var startTranslate = d3.zoomTransform(zoomRect.node()).x;
var endTranslate = 0;
var endScale = 1;
zoomRect.call(zoom).transition()
.duration(1500)
.ease(d3.easeLinear)
.tween("transform", function() {
var interpolateScale = d3.interpolateNumber(startScale,endScale);
var interpolateTranslate = d3.interpolateNumber(startTranslate,endTranslate);
return function(t) {
var t = d3.zoomIdentity.translate(interpolateTranslate(t),0).scale(interpolateScale(t));
console.log(t);
zoomed(t);
}
}).on("end", function() {
d3.select(this).call(zoom.transform, d3.zoomIdentity
.translate(endTranslate, 0)
.scale(endScale));
})
}
function zoomed(transform) {
var t = transform || d3.event.transform;
var xz = t.rescaleX(xSVG);
x = t.rescaleX(x2);
xGroupSVG.call(xAxisSVG.scale(xz));
zoomButton
.attr('x', d => {
return xz(buttonDate)
});
drawCanvas();
}
function type(d, _, columns) {
d.date = parseDate(d.date);
for (var i = 1, n = columns.length, c; i < n; ++i) d[c = columns[i]] = +Math.round(d[c]);
return d;
}
</script>
</body>
</html>
https://d3js.org/d3.v4.min.js