Modify Cartesian DIstortion for D3js V4 - d3.js

I am trying to achieve this effect. The closest I could find an working example of that effect is Cartesian distortion effect which doesn't seem to work with D3 V4. I don't really understand which all lines need to be changed or how to change to make this example compatible with d3js version 4.
jsfiddle
(function chart3() {
console.clear()
var width = 960,
height = 180,
xSteps = d3.range(10, width, 16),
ySteps = d3.range(10, height, 16);
var xFisheye = d3.fisheye.scale(d3.scale.identity).domain([0, width]).focus(360),
yFisheye = d3.scale.linear().domain([0, height]);
var svg = d3.select("#chart3").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(-.5,-.5)");
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height);
var xLine = svg.selectAll(".x")
.data(xSteps)
.enter().append("line")
.attr("class", "x")
.attr("y2", height);
redraw();
svg.on("mousemove", function() {
var mouse = d3.mouse(this);
// HACK ( only for left-side )
xFisheye.focus(mouse[0] - 32); // HACK 1
yFisheye(mouse[1]);
if(mouse[0] > 26) // HACK 2
redraw();
});
function redraw() {
xLine.attr("x1", xFisheye).attr("x2", xFisheye);
}
})();

For what it's worth I just tweaked the d3-fisheye plugin to work with d3 v4. I've also added the fisheye.invert which might be useful.
import * as d3 from 'd3'
const fisheye = {
scale: function (scaleType) {
return d3FisheyeScale(scaleType(), 3, 0)
},
circular: function () {
let radius = 200
let distortion = 2
let k0
let k1
let focus = [0, 0]
function fisheye (d) {
let dx = d.x - focus[0]
let dy = d.y - focus[1]
let dd = Math.sqrt(dx * dx + dy * dy)
if (!dd || dd >= radius) return {x: d.x, y: d.y, z: dd >= radius ? 1 : 10}
let k = k0 * (1 - Math.exp(-dd * k1)) / dd * 0.75 + 0.25
return {x: focus[0] + dx * k, y: focus[1] + dy * k, z: Math.min(k, 10)}
}
function rescale () {
k0 = Math.exp(distortion)
k0 = k0 / (k0 - 1) * radius
k1 = distortion / radius
return fisheye
}
fisheye.radius = function (_) {
if (!arguments.length) return radius
radius = +_
return rescale()
}
fisheye.distortion = function (_) {
if (!arguments.length) return distortion
distortion = +_
return rescale()
}
fisheye.focus = function (_) {
if (!arguments.length) return focus
focus = _
return fisheye
}
return rescale()
}
}
function d3FisheyeScale (scale, d, a) {
function fisheye (_) {
let x = scale(_)
let left = x < a
let range = d3.extent(scale.range())
let min = range[0]
let max = range[1]
let m = left ? a - min : max - a
if (m === 0) m = max - min
return (left ? -1 : 1) * m * (d + 1) / (d + (m / Math.abs(x - a))) + a
}
fisheye.invert = function (xf) {
let left = xf < a
let range = d3.extent(scale.range())
let min = range[0]
let max = range[1]
let m = left ? a - min : max - a
if (m === 0) m = max - min
return scale.invert(a + m * (xf - a) / ((d + 1) * m - (left ? -1 : 1) * d * (xf - a)))
}
fisheye.distortion = function (_) {
if (!arguments.length) return d
d = +_
return fisheye
}
fisheye.focus = function (_) {
if (!arguments.length) return a
a = +_
return fisheye
}
fisheye.copy = function () {
return d3FisheyeScale(scale.copy(), d, a)
}
fisheye.nice = scale.nice
fisheye.ticks = scale.ticks
fisheye.tickFormat = scale.tickFormat
const rebind = function (target, source) {
let i = 1
const n = arguments.length
let method
while (++i < n) {
method = arguments[i]
target[method] = d3Rebind(target, source, source[method])
};
return target
}
function d3Rebind (target, source, method) {
return function () {
var value = method.apply(source, arguments)
return value === source ? target : value
}
}
return rebind(fisheye, scale, 'domain', 'range')
}
export default fisheye
Now to use it:
import fisheye from './fisheye'
import { scaleLinear, scalePow } from 'd3-scale'
const fisheyeLinearScale = fisheye.scale(scaleLinear)
const fisheyePowScale = fisheye.scale(scalePow().exponent(1.1).copy)
const myFisheyeScale = fisheyePowScale.domain(<domain>)
.range(<range>)
.focus(<mouseX>)
.distortion(<deformation>)

Related

Custom easing in d3

I am trying to figure out how can I create custom ease for d3 animation.
const width = 1536;
const height = 720;
const svgns = "http://www.w3.org/2000/svg";
const svg = d3.select("svg");
/*create base svg*/
svg.attr("xmlns", svgns).attr("viewBox", `0 0 ${width} ${height}`);
/*create style*/
const style = d3.select("body").append("style").attr("type", "text/css");
//cubic-bezier(x1, y1, x2, y2) ->x1 and x2 must be between 0 and 1; with negative x1 and x2 provided css reverts to default->0.25, 0.1, 0.25, 1.0
//cubic-bezier --please change the value here to move red and green rect with the exact same velocity
//default-0.42, 0, 1, 1
//easeInSine - 0.12, 0, 0.39, 0; easeOutSine-0.61, 1, 0.88, 1; easeInOutSine-0.37, 0, 0.63, 1 etc.
//0.61, -0.25, 0.88, 1; 0.61, -0.15, 0.88, -1.25 etc.
const cubicBezCurvVal = "0.42, 0, 1, 1";
/*background rect*/
svg
.append("rect")
.attr("class", "vBoxRect")
.attr("width", `${width}`)
.attr("height", `${height}`)
.attr("fill", "#EFEFEF")
.attr("stroke", "black");
/*red rect - css animation+animation timing=> explicit cubic-bezier value*/
svg
.append("rect")
.attr("class", "red")
.attr("x", "0")
.attr("y", `${height / 2 - 40}`)
.attr("height", "50")
.attr("width", "50")
.attr("fill", "red")
.style("--dist", `${width - 50 + "px"}`);
svg
.append("line")
.attr("class", "path1")
.attr("x1", "0")
.attr("y1", () => {
return document.querySelector(".red").y.baseVal.value;
})
.attr("x2", `${width}`)
.attr("y2", () => {
return document.querySelector(".red").y.baseVal.value;
})
.attr("stroke", "red")
.attr("stroke-dasharray", "10");
const keyFrame1 = `
.red {
transform-box: fill-box;
animation-name: move1;
animation-duration: 5s;
animation-iteration-count: 1;
animation-timing-function: cubic-bezier(${cubicBezCurvVal});
animation-direction: normal;
animation-fill-mode: both;
}
#keyframes move1 {
0% {
transform: translateX(0px);
}
100% {
transform: translateX(var(--dist));
}
}
`;
style["_groups"][0][0].innerHTML = keyFrame1;
/*green rect - css animation+animation timing=> calculated progress of cubic-bezier value with javascript*/
svg
.append("rect")
.attr("class", "green")
.attr("x", "0")
.attr("y", `${height / 2 + 40}`)
.attr("height", "50")
.attr("width", "50")
.attr("fill", "green")
.style("--dist", `${width - 50 + "px"}`);
svg
.append("line")
.attr("class", "path2")
.attr("x1", "0")
.attr("y1", () => {
return document.querySelector(".green").y.baseVal.value;
})
.attr("x2", `${width}`)
.attr("y2", () => {
return document.querySelector(".green").y.baseVal.value;
})
.attr("stroke", "green")
.attr("stroke-dasharray", "10");
/*credit - https://blog.maximeheckel.com/posts/cubic-bezier-from-math-to-motion/*/
// create percentage container
const pct = [];
for (let i = 0; i <= 100; i++) {
pct.push(i / 100);
}
//cubic-bezier
//const cubicBezCurvVal = "0.42, 0, 1, 1"
//split bezier curve value
var cleanVal = cubicBezCurvVal.split(",");
//clean space with map -retunrns new array with the function, original array unchnaged
var cleanVal = cleanVal.map((x) => parseFloat(x.replace(/ /g, "")));
//p0
const p0 = {
x: 0,
y: 0
};
//p3
const p3 = {
x: 1,
y: 1
};
//p1
const p1 = {
x: cleanVal[0],
y: cleanVal[1]
};
//p2
const p2 = {
x: cleanVal[2],
y: cleanVal[3]
};
const x0 = p0.x; //=0
const y0 = p0.y; //=0
const x1 = p1.x;
const y1 = p1.y;
const x2 = p2.x;
const y2 = p2.y;
const x3 = p3.x; //=1
const y3 = p3.y; //=1
/*given a time percentage, calculates the x-axis of the cubic bezier graph, i.e. time elpased% */
const x = (t) =>
Math.pow(1 - t, 3) * x0 +
3 * Math.pow(1 - t, 2) * t * x1 +
3 * (1 - t) * Math.pow(t, 2) * x2 +
Math.pow(t, 3) * x3;
/*given a time percentage, calculates the y-axis of the cubic bezier graph, i.e. progres% */
const y = (t) =>
Math.pow(1 - t, 3) * y0 +
3 * Math.pow(1 - t, 2) * t * y1 +
3 * (1 - t) * Math.pow(t, 2) * y2 +
Math.pow(t, 3) * y3;
//penner's easing equation p=f(t)
const c = width - 50; // c of t,b,c,d of penner's equation
const b = 0; // b of t,b,c,d of penner's equation
//create container
const time = []; //to collect values of x(t), i.e. time elapsed %
const progress = []; //to collect values of y(t), i.e. progress %
//get the time first --- goes into keyframe---not dependent on progress,i.e. y(t)
pct.forEach((a) => {
time.push(x(a));
});
//get the progress for each time --- goes into progress --- not dependent on time x(t)
pct.forEach((a) => {
progress.push(y(a) * c + b);
});
//generate keyFrame string
var str = "#keyframes move{";
for (let i = 0; i < time.length; i++) {
var styleStr = `${time[i] * 100}%{ transform:translateX(${
progress[i]
}px);}`;
str += styleStr;
}
const keyFrame2 = `.green {
transform-box: fill-box;
animation-name: move;
animation-duration: 5s;
animation-iteration-count: 1;
animation-timing-function: linear;
animation-direction: normal;
animation-fill-mode: both;
}
${str}}
`;
style["_groups"][0][0].innerHTML += keyFrame2;
/*blue rect for d3*/
function progress1(t) {
//p0
const p0 = {
x: 0,
y: 0
};
//p3
const p3 = {
x: 1,
y: 1
};
//p1
const p1 = {
x: cleanVal[0],
y: cleanVal[1]
};
//p2
const p2 = {
x: cleanVal[2],
y: cleanVal[3]
};
const x0 = p0.x; ///0
const y0 = p0.y; ///0
const x1 = p1.x;
const y1 = p1.y;
const x2 = p2.x;
const y2 = p2.y;
const x3 = p3.x; ////1
const y3 = p3.y; ////1
const progress =
Math.pow(1 - t, 3) * y0 +
3 * Math.pow(1 - t, 2) * t * y1 +
3 * (1 - t) * Math.pow(t, 2) * y2 +
Math.pow(t, 3) * y3;
return t >= 1 ? 1 : progress;
}
svg
.append("rect")
.attr("class", "blue")
.attr("x", "0")
.attr("y", `${height / 2 + 120}`)
.attr("height", "50")
.attr("width", "50")
.attr("fill", "blue")
.each(function (d, i) {
d3.select(this)
.transition()
.duration(5000)
// .ease(progress1(d))
.attr("x", `${width - 50}`);
});
svg
.append("line")
.attr("class", "path3")
.attr("x1", "0")
.attr("y1", () => {
return document.querySelector(".blue").y.baseVal.value;
})
.attr("x2", `${width}`)
.attr("y2", () => {
return document.querySelector(".blue").y.baseVal.value;
})
.attr("stroke", "blue")
.attr("stroke-dasharray", "10");
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
<svg>
</svg>
</body>
<script src="prod.js"></script>
</html>
To elaborate, in CSS, animation-timing function is controlled by cubic-bezier value.
In my element, I have three rect- red, green, and blue.
red rect is moved with an explicit cubic-bezier value animation-timing-function: cubic-bezier(0.42, 0, 1, 1);.
For green rect I have explicitly calculated the keyFrame time% and progress using the following, given a cubic-bezier value. By doing this, I am explicitly telling the green rect to move as per the calculation.
// create percentage container
const pct = [];
for (let i = 0; i <= 100; i++) {
pct.push(i / 100);
}
//cubic-bezier
//const cubicBezCurvVal = "0.42, 0, 1, 1"
//split bezier curve value
var cleanVal = cubicBezCurvVal.split(",");
//clean space with map -retunrns new array with the function, original array unchnaged
var cleanVal = cleanVal.map((x) => parseFloat(x.replace(/ /g, "")));
//p0
const p0 = {
x: 0,
y: 0
};
//p3
const p3 = {
x: 1,
y: 1
};
//p1
const p1 = {
x: cleanVal[0],
y: cleanVal[1]
};
//p2
const p2 = {
x: cleanVal[2],
y: cleanVal[3]
};
const x0 = p0.x; //=0
const y0 = p0.y; //=0
const x1 = p1.x;
const y1 = p1.y;
const x2 = p2.x;
const y2 = p2.y;
const x3 = p3.x; //=1
const y3 = p3.y; //=1
/*given a time percentage, calculates the x-axis of the cubic bezier graph, i.e. time elpased% */
const x = (t) =>
Math.pow(1 - t, 3) * x0 +
3 * Math.pow(1 - t, 2) * t * x1 +
3 * (1 - t) * Math.pow(t, 2) * x2 +
Math.pow(t, 3) * x3;
/*given a time percentage, calculates the y-axis of the cubic bezier graph, i.e. progres% */
const y = (t) =>
Math.pow(1 - t, 3) * y0 +
3 * Math.pow(1 - t, 2) * t * y1 +
3 * (1 - t) * Math.pow(t, 2) * y2 +
Math.pow(t, 3) * y3;
//penner's easing equation p=f(t)
const c = width - 50; // c of t,b,c,d of penner's equation
const b = 0; // b of t,b,c,d of penner's equation
//create container
const time = []; //to collect values of x(t), i.e. time elapsed %
const progress = []; //to collect values of y(t), i.e. progress %
//get the time first --- goes into keyframe---not dependent on progress,i.e. y(t)
pct.forEach((a) => {
time.push(x(a));
});
//get the progress for each time --- goes into progress --- not dependent on time x(t)
pct.forEach((a) => {
progress.push(y(a) * c + b);
});
I also have blue rect and I was wondering how can translate the same cubic-bezier value into a custom -ease function for d3?
I referred to this and came up with following which did not work unfortunately.
function progress1(t) {
//p0
const p0 = {
x: 0,
y: 0
};
//p3
const p3 = {
x: 1,
y: 1
};
//p1
const p1 = {
x: cleanVal[0],
y: cleanVal[1]
};
//p2
const p2 = {
x: cleanVal[2],
y: cleanVal[3]
};
const x0 = p0.x; ///0
const y0 = p0.y; ///0
const x1 = p1.x;
const y1 = p1.y;
const x2 = p2.x;
const y2 = p2.y;
const x3 = p3.x; ////1
const y3 = p3.y; ////1
const progress =
Math.pow(1 - t, 3) * y0 +
3 * Math.pow(1 - t, 2) * t * y1 +
3 * (1 - t) * Math.pow(t, 2) * y2 +
Math.pow(t, 3) * y3;
return t >= 1 ? 1 : progress;

How can I calculate distante between rects in D3?

I'm working on this project and I need to calculate the distance between rects? Some idea?
My goal is to replicate one hart about Covid from this piece from The Washington Post: https://www.washingtonpost.com/graphics/2020/health/coronavirus-herd-immunity-simulation-vaccine/
Below the picture is the code who I used to generate this chart.
Thanks!!
export function main(){
createSVG()
build()
}
let offset = {top: 20, left: 70}
let screenWidth = 800 - offset.left
let screenHeight = 400 - offset.top
function createSVG() {
let container = d3.select('#container')
svg = container.append('svg')
.attr('id', 'canvas')
.attr('width', screenWidth + offset.left)
.attr('height', screenHeight + offset.top)
}
function build() {
let rectWidth = 15
let rectHeight = 15
let randomMovementLimit = 9
let people = []
for(let i = 0; i< 100; i++){
const cellX = i % 10
const randomX = Math.random() * randomMovementLimit - 3
const x = offset.left + cellX * (rectWidth + 10) + randomX
const cellY = Math.floor(i / 10)
const randomY = Math.random() * randomMovementLimit - 3
const y = offset.top + cellY * (rectHeight + 10) + randomY
const infected = Math.random() > 0.5
people.push({x: x, y: y, cellX: cellX, cellY: cellY, infected: infected})
}
console.log(people[0])
const infectedPeople = people.filter
const healtyPeople = people.filter
let rects = svg.selectAll('rect')
.data(people)
.enter()
.append('rect')
.attr('x', function(d,i) {
return d.x
})
.attr('y', function(d,i) {
return d.y
})
.attr('height', rectHeight)
.attr('width', rectWidth)
.style('fill', (d, i) => {
return d.infected ? "red" : "blue"
})
.attr('rx', 5)
}
Calculate distance from center of each rectangle using pythagorean theorem. I think but haven't tested if I remember right that the center of each rectangle is at d.x + rectWidth/2 and d.y - rectHeight/2. Initialize rectWidth and rectHeight outside of the build function so they are within scope.
Example to get newly infected rectangles at distance less than social_dist from the previously infected:
let social_dist = 6;
rects.filter(function(d,i){
for (let p of infectedPeople){
let xDist = (p.x + rectWidth/2) - (d.x + rectWidth/2);
let yDist = (p.y - rectHeight/2) - (d.y - rectHeight/2);
let dist = Math.sqrt( xDist*xDist + yDist*yDist );
if (dist < social_dist){
return true;
}
return false;
});

Unable to see charts for the d3 code. Error in console 't.apply is not a function'

I am trying to use crossfilter.js and d3.js to generate brushes and charts to filter my data. The dates are in the format "2019-04-08T09:07:22.512Z" . I tried different ways to parse through the date and to generate the charts but am unable to do so . I get the following error in the console 't.apply is not a function'. Looking at the error I presume there is a problem with the render and renderAll functions.
d3 version 5.7.0
var cf = crossfilter(json);
var all = cf.groupAll();
var byAlertType = cf.dimension(function(p) {return p['alertType'];});
var byDate = cf.dimension(function (p) { return p['created'].substring(0,10);});
var byHour = cf.dimension(function (p) { return p['created'].substring(11,13);});
var byTimezone = cf.dimension(function (p) { return p['created'].substring(30,39);});
var groupByAlertType = byAlertType.group();
var groupByDate = byDate.group();
var groupByHour = byHour.group();
var groupByTimezone = byTimezone.group();
//data selected, counts by day date hour
groupByAlertType.top(Infinity).forEach(function(p,i){
console.log(p.key + ":" + p.value);
});
groupByDate.top(Infinity).forEach(function(p,i){
console.log(p.key + ":" + p.value);
});
groupByTimezone.top(Infinity).forEach(function(p,i){
console.log(p.key + ":" + p.value);
});
groupByHour.top(Infinity).forEach(function(p,i){
console.log(p.key + ":" + p.value);
});
var charts = [
barChart()
.dimension(byHour)
.group(groupByHour)
.x(d3.scaleLinear()
.domain([0,24])
.rangeRound([0, 10*24])),
barChart()
.dimension(byDate)
.group(groupByDate)
.round(d3.timeDay.round)
.x(d3.scaleTime()
.domain([new Date(2019,4,1),new Date(2019,5,1)])
.rangeRound([0,10*30])),
barChart()
.dimension(byAlertType)
.group(groupByAlertType)
.x(d3.scaleLinear()
.domain([0,24])
.rangeRound([0, 10*24])),
barChart()
.dimension(byTimezone)
.group(groupByTimezone)
.x(d3.scaleLinear()
.domain([0,24])
.rangeRound([0, 10*24])),
];
//charts as they appear in DOM
const viz = d3.selectAll('.chart')
.data([charts])
// render initial list
const list = d3.selectAll('.list')
.data([alertList]);
d3.selectAll('#total')
.text((cf.size()));
renderAll();
function render(method){
d3.select(this).call(method);
}
function renderAll() {
viz.each(render);
list.each(render);
d3.select('#active').text(all.value());
}
window.filter = filters => {
filters.forEach((d, i) => { charts[i].filter(d); });
renderAll();
};
window.reset = i => {
charts[i].filter(null);
renderAll();
};
function alertList(div) {
}
function barChart() {
if (!barChart.id) barChart.id = 0;
let margin = { top: 10, right: 13, bottom: 20, left: 10 };
let x;
let y = d3.scaleLinear().range([100, 0]);
const id = barChart.id++;
const axis = d3.axisBottom();
const brush = d3.brushX();
let brushDirty;
let dimension;
let group;
let round;
let gBrush;
function chart(div) {
const width = x.range()[1];
const height = y.range()[0];
brush.extent([[0, 0], [width, height]]);
y.domain([0, group.top(1)[0].value]);
div.each(function () {
const div = d3.select(this);
let g = div.select('g');
// Create the skeletal chart.
if (g.empty()) {
div.select('.title').append('a')
.attr('href', `javascript:reset(${id})`)
.attr('class', 'reset')
.text('reset')
.style('display', 'none');
g = div.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
g.append('clipPath')
.attr('id', `clip-${id}`)
.append('rect')
.attr('width', width)
.attr('height', height);
g.selectAll('.bar')
.data(['background', 'foreground'])
.enter().append('path')
.attr('class', d => `${d} bar`)
.datum(group.all());
g.selectAll('.foreground.bar')
.attr('clip-path', `url(#clip-${id})`);
g.append('g')
.attr('class', 'axis')
.attr('transform', `translate(0,${height})`)
.call(axis);
// Initialize the brush component with pretty resize handles.
gBrush = g.append('g')
.attr('class', 'brush')
.call(brush);
gBrush.selectAll('.handle--custom')
.data([{ type: 'w' }, { type: 'e' }])
.enter().append('path')
.attr('class', 'brush-handle')
.attr('cursor', 'ew-resize')
.attr('d', resizePath)
.style('display', 'none');
}
// Only redraw the brush if set externally.
if (brushDirty !== false) {
const filterVal = brushDirty;
brushDirty = false;
div.select('.title a').style('display', d3.brushSelection(div) ? null : 'none');
if (!filterVal) {
g.call(brush);
g.selectAll(`#clip-${id} rect`)
.attr('x', 0)
.attr('width', width);
g.selectAll('.brush-handle').style('display', 'none');
renderAll();
} else {
const range = filterVal.map(x);
brush.move(gBrush, range);
}
}
g.selectAll('.bar').attr('d', barPath);
});
function barPath(groups) {
const path = [];
let i = -1;
const n = groups.length;
let d;
while (++i < n) {
d = groups[i];
path.push('M', x(d.key), ',', height, 'V', y(d.value), 'h9V', height);
}
return path.join('');
}
function resizePath(d) {
const e = +(d.type === 'e');
const x = e ? 1 : -1;
const y = height / 3;
return `M${0.5 * x},
${y}A6,6 0 0 ${e} ${6.5 * x},
${y + 6}V${2 * y - 6}A6,6 0 0 ${e} ${0.5 * x},${2 * y}ZM${2.5 * x},${y + 8}V${2 * y - 8}M${4.5 * x},${y + 8}V${2 * y - 8}`;
}
}
brush.on('start.chart', function () {
const div = d3.select(this.parentNode.parentNode.parentNode);
div.select('.title a').style('display', null);
});
brush.on('brush.chart', function () {
const g = d3.select(this.parentNode);
const brushRange = d3.event.selection || d3.brushSelection(this); // attempt to read brush range
const xRange = x && x.range(); // attempt to read range from x scale
let activeRange = brushRange || xRange; // default to x range if no brush range available
const hasRange = activeRange &&
activeRange.length === 2 &&
!isNaN(activeRange[0]) &&
!isNaN(activeRange[1]);
if (!hasRange) return; // quit early if we don't have a valid range
// calculate current brush extents using x scale
let extents = activeRange.map(x.invert);
// if rounding fn supplied, then snap to rounded extents
// and move brush rect to reflect rounded range bounds if it was set by user interaction
if (round) {
extents = extents.map(round);
activeRange = extents.map(x);
if (
d3.event.sourceEvent &&
d3.event.sourceEvent.type === 'mousemove'
) {
d3.select(this).call(brush.move, activeRange);
}
}
// move brush handles to start and end of range
g.selectAll('.brush-handle')
.style('display', null)
.attr('transform', (d, i) => `translate(${activeRange[i]}, 0)`);
// resize sliding window to reflect updated range
g.select(`#clip-${id} rect`)
.attr('x', activeRange[0])
.attr('width', activeRange[1] - activeRange[0]);
// filter the active dimension to the range extents
dimension.filterRange(extents);
// re-render the other charts accordingly
renderAll();
});
brush.on('end.chart', function () {
// reset corresponding filter if the brush selection was cleared
// (e.g. user "clicked off" the active range)
if (!d3.brushSelection(this)) {
reset(id);
}
});
chart.margin = function (_) {
if (!arguments.length) return margin;
margin = _;
return chart;
};
chart.x = function (_) {
if (!arguments.length) return x;
x = _;
axis.scale(x);
return chart;
};
chart.y = function (_) {
if (!arguments.length) return y;
y = _;
return chart;
};
chart.dimension = function (_) {
if (!arguments.length) return dimension;
dimension = _;
return chart;
};
chart.filter = _ => {
if (!_) dimension.filterAll();
brushDirty = _;
return chart;
};
chart.group = function (_) {
if (!arguments.length) return group;
group = _;
return chart;
};
chart.round = function (_) {
if (!arguments.length) return round;
round = _;
return chart;
};
chart.gBrush = () => gBrush;
return chart;
}
};
};

Fill not working with areaRadial() generated shape

I'm currently working on a thing which is supposed to look like a lava lamp later. Unfortunately, I'm already failing at the beginning:
I managed to create randomly generated blobs, but I just can't apply a fill. The stroke works just fine.
here is the relevant code which creates the blobs:
var ctx = this; // context
this.path = d3.areaRadial()
.angle(function(d) {return d.theta})
.radius(function(d) {return d.r})
.curve(d3.curveCatmullRomClosed.alpha(1));
// create the blob
this.create = function() {
var anchors = [];
for (var i = 0; i < ctx.options.anchors; i++) {
var currTheta = i * (360 / ctx.options.anchors) + rand(ctx.options.spread.theta);
var currRadians = Math.radians(currTheta);
var currRadius = ctx.options.radius + rand(ctx.options.spread.r);
anchors.push({theta: currRadians, r: currRadius});
}
var pathData = ctx.path(anchors);
d3.select(ctx.options.target).append('path')
.attr('d', pathData)
.attr('class', 'blob')
.style('opacity', ctx.options.opacity)
.style('transform', 'translate(' + ctx.x + 'px, ' + ctx.y + 'px)')
.attr('transform', 'translate(' + ctx.x + 'px, ' + ctx.y + 'px)');
console.log(pathData);
}
function rand(x) // creates a random number between -0.5 * x and 0.5 * x
Full code: https://codepen.io/normanwink/pen/BrMVrE
Thanks!
That's the expected behaviour, since you're using radius, which is:
Equivalent to area.y, except the accessor returns the radius: the distance from the origin ⟨0,0⟩.
And for area.y:
If y is specified, sets y0 to y and y1 to null and returns this area generator. If y is not specified, returns the current y0 accessor. (emphasis mine)
Therefore, you probably want outerRadius here.
this.path = d3.areaRadial()
.angle(function(d) {
return d.theta
})
.outerRadius(function(d) {
return d.r
})
.curve(d3.curveCatmullRomClosed.alpha(1));
This is your code with that change only:
(function() {
"use strict";
// window size
var windowX = window.innerWidth;
var windowY = window.innerHeight;
var svgX = windowX;
var svgY = windowY;
var blobCount = 1;
function init() {
// console.log(new Blob());
var svg = d3.select('#svg')
.attr('viewBox', '0 0 ' + svgX + ' ' + svgY)
.attr('preserveAspectRatio', 'xMinYMin meet');
for (var i = 0; i < blobCount; i++) {
var newBlob = new Blob(svgX * 0.5, svgY * 0.5);
}
}
function Blob(x, y) {
var ctx = this; // context
this.options = {
anchors: 8,
breathe: 30,
fill: '#ffffff',
opacity: 0.5,
radius: 150,
spread: {
theta: 10, // angle
r: 300 // radius
},
target: '#svg',
};
this.x = x || 0;
this.y = y || 0;
this.path = d3.areaRadial()
.angle(function(d) {return d.theta})
.outerRadius(function(d) {return d.r})
.curve(d3.curveCatmullRomClosed.alpha(1));
// create the blob
this.create = function() {
var anchors = [];
for (var i = 0; i < ctx.options.anchors; i++) {
var currTheta = i * (360 / ctx.options.anchors) + rand(ctx.options.spread.theta);
var currRadians = Math.radians(currTheta);
var currRadius = ctx.options.radius + rand(ctx.options.spread.r);
anchors.push({theta: currRadians, r: currRadius});
}
var pathData = ctx.path(anchors);
d3.select(ctx.options.target).append('path')
.attr('d', pathData)
.attr('class', 'blob')
.style('opacity', ctx.options.opacity)
.style('transform', 'translate(' + ctx.x + 'px, ' + ctx.y + 'px)')
.attr('transform', 'translate(' + ctx.x + 'px, ' + ctx.y + 'px)');
}
// update position and anchor movement
this.update = function() {
}
// apply changes
this.render = function() {
}
this.create();
}
function rand(i) {
return (Math.random()-0.5) * (i || 1);
}
Math.radians = function(degrees) {
return degrees * Math.PI / 180;
};
// init when ready
init();
})();
#svg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.blob {
fill: blue; /* why is this not working? */
stroke: red;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg id="svg"></svg>
Your problem is your pathData , because if you replace your .attr('d', pathData) with .attr('d', 'M37,17v15H14V17z M50,0H0v50h50z') which is a valid path, your fill is working properly.
I will continue searching the issue, just want to give you a hint, maybe you will find the issue faster then me. :)

How to make D3 gauge chart in Qlikview extension

I have made a gauge chart using D3.js and it is working perfectly. However, when I am trying to add the same javascript code to render the same chart in a Qlikview extension, it is not working.
Can anyone please help me to find out where I am going wrong? I am attaching the code of the Script.js file below:
Qva.LoadScript("QlikView/Examples/Gauge/d3.min.js", function () {
/*
function loadGoogleCoreChart() {
google.load('visualization', '1', {
packages: ['corechart'],
callback: googleCoreChartLoaded
});
}
function googleCoreChartLoaded() {
*/
Qva.AddExtension('QlikView/Examples/Gauge', function () {
//this.Element.innerHTML = "<script type=\"text/javascript\" src=\"http://d3js.org/d3.v2.min.js\"></script>"
//this.Element.innerHTML = "";
var gauge = function(container, configuration) {
var that = {};
var config = {
size : 200,
clipWidth : 200,
clipHeight : 110,
ringInset : 20,
ringWidth : 20,
pointerWidth : 10,
pointerTailLength : 5,
pointerHeadLengthPercent : 0.9,
minValue : 0,
maxValue : 10,
minAngle : -90,
maxAngle : 90,
transitionMs : 750,
majorTicks : 5,
labelFormat : d3.format(',g'),
labelInset : 10,
arcColorFn : d3.interpolateHsl(d3.rgb('#e8e2ca'), d3.rgb('#3e6c0a'))
};
var range = undefined;
var r = undefined;
var pointerHeadLength = undefined;
var value = 0;
var svg = undefined;
var arc = undefined;
var scale = undefined;
var ticks = undefined;
var tickData = undefined;
var pointer = undefined;
var donut = d3.layout.pie();
function deg2rad(deg) {
return deg * Math.PI / 180;
}
function newAngle(d) {
var ratio = scale(d);
var newAngle = config.minAngle + (ratio * range);
return newAngle;
}
function configure(configuration) {
var prop = undefined;
for ( prop in configuration ) {
config[prop] = configuration[prop];
}
range = config.maxAngle - config.minAngle;
r = config.size / 2;
pointerHeadLength = Math.round(r * config.pointerHeadLengthPercent);
// a linear scale that maps domain values to a percent from 0..1
scale = d3.scale.linear()
.range([0,1])
.domain([config.minValue, config.maxValue]);
ticks = scale.ticks(config.majorTicks);
tickData = d3.range(config.majorTicks).map(function() {return 1/config.majorTicks;});
arc = d3.svg.arc()
.innerRadius(r - config.ringWidth - config.ringInset)
.outerRadius(r - config.ringInset)
.startAngle(function(d, i) {
var ratio = d * i;
return deg2rad(config.minAngle + (ratio * range));
})
.endAngle(function(d, i) {
var ratio = d * (i+1);
return deg2rad(config.minAngle + (ratio * range));
});
}
that.configure = configure;
function centerTranslation() {
return 'translate('+r +','+ r +')';
}
function isRendered() {
return (svg !== undefined);
}
that.isRendered = isRendered;
function render(newValue) {
svg = d3.select(container)
.append('svg:svg')
.attr('class', 'gauge')
.attr('width', config.clipWidth)
.attr('height', config.clipHeight);
var centerTx = centerTranslation();
var arcs = svg.append('g')
.attr('class', 'arc')
.attr('transform', centerTx);
arcs.selectAll('path')
.data(tickData)
.enter().append('path')
.attr('fill', function(d, i) {
return config.arcColorFn(d * i);
})
.attr('d', arc);
var lg = svg.append('g')
.attr('class', 'label')
.attr('transform', centerTx);
lg.selectAll('text')
.data(ticks)
.enter().append('text')
.attr('transform', function(d) {
var ratio = scale(d);
var newAngle = config.minAngle + (ratio * range);
return 'rotate(' +newAngle +') translate(0,' +(config.labelInset - r) +')';
})
.text(config.labelFormat);
var lineData = [ [config.pointerWidth / 2, 0],
[0, -pointerHeadLength],
[-(config.pointerWidth / 2), 0],
[0, config.pointerTailLength],
[config.pointerWidth / 2, 0] ];
var pointerLine = d3.svg.line().interpolate('monotone');
var pg = svg.append('g').data([lineData])
.attr('class', 'pointer')
.attr('transform', centerTx);
pointer = pg.append('path')
.attr('d', pointerLine/*function(d) { return pointerLine(d) +'Z';}*/ )
.attr('transform', 'rotate(' +config.minAngle +')');
update(newValue === undefined ? 0 : newValue);
}
that.render = render;
function update(newValue, newConfiguration) {
if ( newConfiguration !== undefined) {
configure(newConfiguration);
}
var ratio = scale(newValue);
var newAngle = config.minAngle + (ratio * range);
pointer.transition()
.duration(config.transitionMs)
.ease('elastic')
.attr('transform', 'rotate(' +newAngle +')');
}
that.update = update;
configure(configuration);
return that;
};
function onDocumentReady() {
var powerGauge = gauge(this.Element, {
size: 300,
clipWidth: 300,
clipHeight: 300,
ringWidth: 60,
maxValue: 10,
transitionMs: 4000,
});
alert("Some Text");
powerGauge.render();
function updateReadings() {
// just pump in random data here...
powerGauge.update(Math.random() * 10);
}
// every few seconds update reading values
updateReadings();
setInterval(function() {
updateReadings();
}, 5 * 1000);
}
if ( !window.isLoaded ) {
window.addEventListener("load", function() {
onDocumentReady();
}, false);
} else {
onDocumentReady();
}
});
});
Please help me with this as I am unable to find out the glitch.

Resources