For a hobby project I started working with ReactJS in combination with D3. The intention was to shape the current crypto market in the form of bubbles. Now this has been achieved, but I am not satisfied with the performance here. Is there a way to make 3 nodes run smoother so that the UI becomes more practiced? Should I look at html canvas?
The code I wrote:
import "../styles/style.css";
import $ from "jquery";
import * as d3 from "d3";
import axios from "axios";
export default class Bubbles extends Component {
constructor() {
super();
this.state = {
isLoading: true, cryptoData: []
};
}
componentDidMount() {
console.debug("After mount! Let's load data from API...");
axios.get('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&per_page=50')
.then(response => {
this.setState({ cryptoData: response.data });
this.setState({ isLoading: false });
});
}
render() {
const { isLoading, cryptoData } = this.state;
const coins = [];
var data = [];
for (let i = 0; i < cryptoData.length; i++) {
var coin = cryptoData[i];
var minplus = String(coin.market_cap_change_percentage_24h);
if (minplus.includes("-")) {
coin["color"] = "red";
} else {
coin["color"] = "green";
}
coins.push(coin);
}
coins.forEach((coin) => {
var text = coin.symbol;
var r = coin.market_cap / 2000000000;
if (r < 5) {
r = 10;
}
data.push({
text: text,
category: coin.price_change_percentage_24h + "%",
image: coin.image,
color: coin.color,
r: r,
});
});
function collide(alpha) {
var quadtree = d3.geom.quadtree(data);
return function (d) {
var r = d.r + 10,
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function (quad, x1, y1, x2, y2) {
if (quad.point && quad.point !== d) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.r + quad.point.r;
if (l < r) {
l = ((l - r) / l) * (1 + alpha);
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
const bubbleCloud = (element) => {
var container = d3.select(".bubble-cloud");
var $container = $(".bubble-cloud");
var containerWidth = $container.width();
var containerHeight = $container.height();
var svgContainer = container
.append("svg")
.attr("width", containerWidth)
.attr("height", containerHeight);
// prepare layout
var force = d3.layout
.force()
.size([containerWidth, containerHeight])
.gravity(0)
.charge(0)
.friction(1);
// load data
force.nodes(data).start();
// create item groups
var node = svgContainer
.selectAll(".node")
.data(data)
.enter()
.append("g")
.attr("class", "node")
.call(force.drag);
var zoom = d3.behavior
.zoom()
.scaleExtent([1, 50])
.on("zoom", function () {
var e = d3.event,
tx = Math.min(
0,
Math.max(e.translate[0], containerWidth - containerWidth * e.scale)
),
ty = Math.min(
0,
Math.max(e.translate[1], containerHeight - containerHeight * e.scale)
);
zoom.translate([tx, ty]);
node.attr(
"transform",
["translate(" + [tx, ty] + ")", "scale(" + e.scale + ")"].join(" ")
);
});
node.call(zoom);
// create circles
var defs = node.append("defs");
function makeFiter(id, color) {
//make a filter if filter id not present
if (defs.selectAll("#" + id).empty()) {
var filter = defs
.append("filter")
.attr("id", id)
.attr("height", "300%")
.attr("width", "300%")
.attr("x", "-100%")
.attr("y", "-100%");
filter
.append("feGaussianBlur")
.attr("in", "SourceAlpha")
.attr("stdDeviation", 10)
.attr("result", "blur");
filter
.append("feOffset", 0)
.attr("in", "blur")
.attr("result", "offsetBlur");
filter
.append("feFlood")
.attr("in", "offsetBlur")
.attr("flood-color", color)
.attr("flood-opacity", "1")
.attr("result", "offsetColor");
filter
.append("feComposite")
.attr("in", "offsetColor")
.attr("in2", "offsetBlur")
.attr("operator", "in")
.attr("result", "offsetBlur");
var feMerge = filter.append("feMerge");
feMerge.append("feMergeNode").attr("in", "offsetBlur");
feMerge.append("feMergeNode").attr("in", "SourceGraphic");
}
return "url(#" + id + ")"; //return the filter id
}
function getFilter(d) {
if (d.color === "red") {
return makeFiter("fill-text-red", "red");
} else if (d.color === "green") {
return makeFiter("fill-text-green", "green");
}
}
defs
.selectAll(null)
.data(data)
.enter()
.append("pattern")
.attr("id", function (d) {
return d.image;
})
.attr("height", "100%")
.attr("width", "100%")
.attr("patternContentUnits", "objectBoundingBox")
.append("image")
.attr("height", 1)
.attr("width", 1)
.attr("preserveAspectRatio", "none")
.attr("xlink:href", function (d) {
return d.image;
});
node
.append("circle")
.attr("r", 1e-6)
.style("fill", function (d) {
return "url(#" + d.image + ")";
})
.attr("filter", function (d) {
return getFilter(d);
})
.on("mouseover", function (d) {
d3.select(this)
.attr("r", 10)
.style("stroke", "white")
.style("cursor", "pointer");
})
.on("mouseout", function (d) {
d3.select(this).attr("r", 5.5).style("stroke", "none");
});
// create labels
node
.append("text")
.text(function (d) {
return d.text;
})
.classed("text", true)
.style({
fill: "#141823 ",
"text-anchor": "middle",
"font-size": "1vw",
"font-weight": "bold",
"text-transform": "uppercase",
"font-family": "Open Sans, sans-serif",
});
//.attr("stroke", "black")
//.attr("stroke-width", "1px");
node
.append("text")
.text(function (d) {
return d.category;
})
.classed("category", true)
.style("fill", function (d) {
return d.category.match("-") ? "red" : "green";
})
.style({
"text-anchor": "middle",
"font-size": "12px",
"font-weight": "bold",
"text-transform": "uppercase",
"font-family": "Open Sans, sans-serif",
});
//.attr("stroke", "black")
//.attr("stroke-width", "1px");
node
//.append("line")
//.classed("line", true)
//.attr({
// x1: 0,
// y1: 0,
// x2: 0,
// y2: 0,
//})
.attr("stroke-width", 1)
.attr("stroke", function (d) {
return d.stroke;
});
// put circle into movement
force.on("tick", function (e) {
d3.selectAll("circle")
.each(collide(0.1))
.attr("r", function (d) {
return d.r;
})
.attr("cx", function (d) {
// boundaries
if (d.x <= d.r) {
d.x = d.r + 1;
}
if (d.x >= containerWidth - d.r) {
d.x = containerWidth - d.r - 1;
}
return d.x;
})
.attr("cy", function (d) {
// boundaries
if (d.y <= d.r) {
d.y = d.r + 1;
}
if (d.y >= containerHeight - d.r) {
d.y = containerHeight - d.r - 1;
}
return d.y;
});
d3.selectAll("line").attr({
x1: function (d) {
return d.x - d.r + 10;
},
y1: function (d) {
return d.y;
},
x2: function (d) {
return d.x + d.r - 10;
},
y2: function (d) {
return d.y;
},
});
d3.selectAll(".text")
.attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y - 10;
});
d3.selectAll(".category")
.attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y + 20;
});
force.alpha(0.1);
});
};
if(isLoading){
return <div className="App">Loading...</div>;
}
return (
<div>
<div className="bubble-cloud" ref={bubbleCloud}></div>
<table className="styled-table">
<thead>
<tr>
<th>#</th>
<th>COIN</th>
<th>NAAM</th>
<th>PRIJS</th>
<th>ALL TIME HIGH</th>
<th>24u</th>
<th>VOLUME</th>
<th>MARKT KAPITALISATIE</th>
<th>TRADE</th>
</tr>
</thead>
<tbody>
{coins.map((coin) => {
var refLink =
"https://www.binance.com/en/trade/" +
coin.symbol +
"_USDT?layout=pro&ref=AISWVEO9&type=spot";
return (
<tr key={coin.id}>
<td>{coin.market_cap_rank}</td>
<td>
<img src={coin.image} alt="icon" height="50px"/>
</td>
<td>{coin.name}</td>
<td>${coin.current_price}</td>
<td>${coin.ath}</td>
<td>
{coin.price_change_percentage_24h}
%
</td>
<td>{coin.total_volume}</td>
<td>US ${coin.market_cap}</td>
<td>
<a href={refLink}>
<img
border="0"
alt="Binance"
src="https://user-images.githubusercontent.com/12424618/54043975-b6cdb800-4182-11e9-83bd-0cd2eb757c6e.png"
width="20"
height="20"
className="refImg"
/>
</a>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}
}
Im hosting my project on this website
Maybe you guys can point me in the right direction?
I make a copy of crypto bubbles where I want the coins to float around like bubbles now I run into the problem that a squares are completely filled and some are not this is because of the shadow? How do I solve this?
import React, { Component } from "react";
import "../styles/style.css";
import $ from "jquery";
import * as d3 from "d3";
const crypto = require("../data/cryptoData.json");
const coins = [];
var data = [];
for (let i = 0; i < crypto.length; i++) {
var coin = crypto[i];
var minplus = String(coin.market_data.price_change_percentage_24h);
if (minplus.includes("-")) {
coin["color"] = "red";
} else {
coin["color"] = "green";
}
coins.push(coin);
}
coins.forEach((coin) => {
var text = coin.symbol;
var r = coin.market_data.market_cap.usd / 3500000000;
if (r < 5) {
r = 20;
}
data.push({
text: text,
category: coin.market_data.price_change_percentage_24h + "%",
image: coin.image.large,
color: coin.color,
r: r,
r_change_1: coin.market_data.market_cap.USD / 3500000000,
r_change_2: coin.market_data.market_cap.USD / 3500000000,
});
});
function collide(alpha) {
var quadtree = d3.geom.quadtree(data);
return function (d) {
var r = d.r + 10,
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function (quad, x1, y1, x2, y2) {
if (quad.point && quad.point !== d) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.r + quad.point.r;
if (l < r) {
l = ((l - r) / l) * (1 + alpha);
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
const bubbleCloud = (element) => {
var container = d3.select(".bubble-cloud");
var $container = $(".bubble-cloud");
var containerWidth = $container.width();
var containerHeight = $container.height();
var svgContainer = container
.append("svg")
.attr("width", containerWidth)
.attr("height", containerHeight)
.call(
d3.behavior.zoom().on("zoom", function () {
node.attr(
"transform",
"translate(" +
d3.event.translate +
")" +
" scale(" +
d3.event.scale +
")"
);
})
);
// prepare layout
var force = d3.layout
.force()
.size([containerWidth, containerHeight])
.gravity(0)
.charge(0)
.friction(1);
// load data
force.nodes(data).start();
// create item groups
var node = svgContainer
.selectAll(".node")
.data(data)
.enter()
.append("g")
.attr("class", "node")
.call(force.drag);
// create circles
var defs = node.append("defs");
function makeFiter(id, color) {
//make a filter if filter id not present
if (defs.selectAll("#" + id).empty()) {
var filter = defs.append("filter").attr("id", id).attr("height", "130%");
filter
.append("feGaussianBlur")
.attr("in", "SourceAlpha")
.attr("stdDeviation", 20)
.attr("result", "blur");
filter
.append("feOffset", 0)
.attr("in", "blur")
.attr("result", "offsetBlur");
filter
.append("feFlood")
.attr("in", "offsetBlur")
.attr("flood-color", color)
.attr("flood-opacity", "1")
.attr("result", "offsetColor");
filter
.append("feComposite")
.attr("in", "offsetColor")
.attr("in2", "offsetBlur")
.attr("operator", "in")
.attr("result", "offsetBlur");
var feMerge = filter.append("feMerge");
feMerge.append("feMergeNode").attr("in", "offsetBlur");
feMerge.append("feMergeNode").attr("in", "SourceGraphic");
}
return "url(#" + id + ")"; //return the filter id
}
function getFilter(d) {
if (d.color === "red") {
return makeFiter("fill-text-red", "red");
} else if (d.color === "green") {
return makeFiter("fill-text-green", "green");
}
}
defs
.selectAll(null)
.data(data)
.enter()
.append("pattern")
.attr("id", function (d) {
return d.image;
})
.attr("height", "100%")
.attr("width", "100%")
.attr("patternContentUnits", "objectBoundingBox")
.append("image")
.attr("height", 1)
.attr("width", 1)
.attr("preserveAspectRatio", "none")
.attr("xlink:href", function (d) {
return d.image;
});
node
.append("circle")
.attr("r", 1e-6)
.style("fill", function (d) {
return "url(#" + d.image + ")";
})
.attr("filter", function (d) {
return getFilter(d);
});
// create labels
node
.append("text")
.text(function (d) {
return d.text;
})
.classed("text", true)
.style({
fill: "#ffffff",
"text-anchor": "middle",
"font-size": "1vw",
"font-weight": "bold",
"text-transform": "uppercase",
"font-family": "Tahoma, Arial, sans-serif",
})
.attr("stroke", "black")
.attr("stroke-width", "1px");
node
.append("text")
.text(function (d) {
return d.category;
})
.classed("category", true)
.style({
fill: "#ffffff",
"text-anchor": "middle",
"font-size": "12px",
"font-weight": "bold",
"text-transform": "uppercase",
"font-family": "Tahoma, Arial, sans-serif",
})
.attr("stroke", "black")
.attr("stroke-width", "1px");
node
//.append("line")
//.classed("line", true)
//.attr({
// x1: 0,
// y1: 0,
// x2: 0,
// y2: 0,
//})
.attr("stroke-width", 1)
.attr("stroke", function (d) {
return d.stroke;
});
// put circle into movement
force.on("tick", function (e) {
d3.selectAll("circle")
.each(collide(0.1))
.attr("r", function (d) {
return d.r;
})
.attr("cx", function (d) {
// boundaries
if (d.x <= d.r) {
d.x = d.r + 1;
}
if (d.x >= containerWidth - d.r) {
d.x = containerWidth - d.r - 1;
}
return d.x;
})
.attr("cy", function (d) {
// boundaries
if (d.y <= d.r) {
d.y = d.r + 1;
}
if (d.y >= containerHeight - d.r) {
d.y = containerHeight - d.r - 1;
}
return d.y;
});
d3.selectAll("line").attr({
x1: function (d) {
return d.x - d.r + 10;
},
y1: function (d) {
return d.y;
},
x2: function (d) {
return d.x + d.r - 10;
},
y2: function (d) {
return d.y;
},
});
d3.selectAll(".text")
.attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y - 10;
});
d3.selectAll(".category")
.attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y + 20;
});
force.alpha(0.1);
});
};
class Bubbles extends Component {
render() {
return <div className="bubble-cloud" ref={bubbleCloud}></div>;
}
}
export default Bubbles;
I want it to look like the Ethereum image but have no idea what i have to change...
What properties need to be changed?
Use a smaller stdDeviation or enlarge your filter region. Something like this might work:
var filter = defs.append("filter").attr("id", id)
.attr("height", "300%")
.attr("width", "300%")
.attr("x", "-100%")
.attr("y", "-100%");
An alternative is to express the drop shadows as a % of the box size rather than in absolute units. Something like this:
var filter = defs.append("filter").attr("id", id)
.attr("primitiveUnits", "objectBoundingBox");
filter
.append("feGaussianBlur")
.attr("in", "SourceAlpha")
.attr("stdDeviation", "0.1")
.attr("result", "blur");
I have created 2 level sunburst chart on http://test.smokethis.com/wheel/ which is working perfectly.
But i want to validate my chart to show only 10 child of a particular parent and an additional node called "More child".
The behaviour on "More child" will be same as on click on parent. Which reveal all child of parent.
Image link of current situation
Image link of Resultant behaviour.Which i want
var d3Data = JSON.parse(jQuery('body .Jsondata').text())[0];
const m0 = {
id: "123",
variables: [
{
name: "chart",
inputs: ["partition","data","d3","DOM","width","color","arc","format","radius"],
value: (function(partition,data,d3,DOM,width,color,arc,format,radius)
{
var formatNumber = d3.format(",d");
var b = {
w: 140, h: 30, s: 3, t: 10
};
const root = partition(data);
root.each(d => d.current = d);
const svg = d3.select(DOM.svg(width, width))
.style("width", "100%")
.style("height", "auto")
.style("font", "20px sans-serif");
const g = svg.append("g")
.attr("transform", `translate(${width / 2},${width / 2})`);
const path = g.append("g")
.selectAll("path")
.data(root.descendants().slice(1))
.enter().append("path")
.attr("fill", d => { while (d.depth > 1) d = d.parent; console.log('d value--',d); return (typeof d.data.color != 'undefined' && d.data.color != '') ? d.data.color : color(d.data.name); })
.attr("fill-opacity", d => arcVisible(d.current) ? (d.children ? 0.6 : 0.4) : 0)
.attr("d", d => arc(d.current));
path.filter(d => d.children)
.style("cursor", "pointer")
.on("click", clicked);
path.append("title")
.text(d => `${d.ancestors().map(d => d.data.name).reverse().join("/")}\n${format(d.value)}`);
const label = g.append("g")
.attr("pointer-events", "none")
.attr("text-anchor", "middle")
.style("user-select", "none")
.selectAll("text")
.data(root.descendants().slice(1))
.enter().append("text")
.attr("dy", "0.35em")
.attr("fill-opacity", d => +labelVisible(d.current))
.attr("transform", d => labelTransform(d.current))
.text(d => d.data.name);
const parent = g.append("circle")
.datum(root)
.attr("r", radius)
.attr("fill", "none")
.attr("pointer-events", "all")
.on("click", clicked);
initializeBreadcrumbTrail();
return svg.node();
function clicked(p) {
updateBreadcrumbs(getParents(p),p.value);
var level = p.ancestors().map(p => p.data.name).reverse();
// var str = '';
// if(level.length > 1){
// for(var i=0;i<level.length;i++){
// str += '<span class="str_att show-pointer" style="background-color: #8cb125;margin-right:10px;border-radius: 5px;padding: 5px 10px;">'+level[i]+'</span>';
// }
// }
//jQuery('.breadCrumb').html(str);
console.log('level-=-',level);
parent.datum(p.parent || root);
root.each(d => d.target = {
x0: Math.max(0, Math.min(1, (d.x0 - p.x0) / (p.x1 - p.x0))) * 2 * Math.PI,
x1: Math.max(0, Math.min(1, (d.x1 - p.x0) / (p.x1 - p.x0))) * 2 * Math.PI,
y0: Math.max(0, d.y0 - p.depth),
y1: Math.max(0, d.y1 - p.depth)
});
const t = g.transition().duration(750);
//console.log('p',p);
// Transition the data on all arcs, even the ones that aren’t visible,
// so that if this transition is interrupted, entering arcs will start
// the next transition from the desired position.
path.transition(t)
.tween("data", d => {
const i = d3.interpolate(d.current, d.target);
return t => d.current = i(t);
})
.filter(function(d) {
return +this.getAttribute("fill-opacity") || arcVisible(d.target);
})
.attr("fill-opacity", d => arcVisible(d.target) ? (d.children ? 0.6 : 0.4) : 0)
.attrTween("d", d => () => arc(d.current));
label.filter(function(d) {
return +this.getAttribute("fill-opacity") || labelVisible(d.target);
}).transition(t)
.attr("fill-opacity", d => +labelVisible(d.target))
.attrTween("transform", d => () => labelTransform(d.current));
}
function initializeBreadcrumbTrail() {
// Add the svg area.
var trail = d3.select(".breadCrumb").append("svg:svg")
.attr("width", width)
.attr("height", width/10)
.attr("id", "trail");
// Add the label at the end, for the percentage.
trail.append("svg:text")
.attr("id", "endlabel")
.style("fill", "#000");
}
function updateBreadcrumbs(nodeArray, percentageString) {
// Data join; key function combines name and depth (= position in sequence).
var g = d3.select("#trail")
.selectAll("g").on("click", clicked)
.data(nodeArray, function(x) { return percentageString + x.data.name + x.depth; });
// Add breadcrumb and label for entering nodes.
var entering = g.enter().append("svg:g");
entering.append("svg:polygon")
.attr("points", breadcrumbPoints)
.style("fill", function(x) { return color(x.data.name); });
entering.append("svg:text")
.attr("x", (b.w + b.t) / 2)
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function(x) { return x.data.name; });
entering.attr("transform", function(x, i) { return "translate(" + i* (b.w + b.s) + ", 0)"; });
// Remove exiting nodes.
g.exit().remove();
// Now move and update the percentage at the end.
d3.select("#trail").select("#endlabel")
.attr("x", (nodeArray.length + 0.5) * (b.w + b.s))
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(percentageString);
// Make the breadcrumb trail visible, if it's hidden.
d3.select("#trail")
.style("visibility", "");
}
function breadcrumbPoints(x, i) {
var points = [];
points.push("0,0");
points.push(b.w + ",0");
points.push(b.w + b.t + "," + (b.h / 2));
points.push(b.w + "," + b.h);
points.push("0," + b.h);
if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
points.push(b.t + "," + (b.h / 2));
}
return points.join(" ");
}
function arcVisible(d) {
return d.y1 <= 3 && d.y0 >= 1 && d.x1 > d.x0;
}
function labelVisible(d) {
return d.y1 <= 3 && d.y0 >= 1 && (d.y1 - d.y0) * (d.x1 - d.x0) > 0.03;
}
function labelTransform(d) {
const x = (d.x0 + d.x1) / 2 * 180 / Math.PI;
const y = (d.y0 + d.y1) / 2 * radius;
return `rotate(${x - 90}) translate(${y},0) rotate(${x < 180 ? 0 : 180})`;
}
function getParents(a){
var nodeArray = [a];
while(a.parent){
nodeArray.push(a.parent);
a = a.parent
}
return nodeArray.reverse();
}
}
)
},
{
name: "data",
value: (function(){
return d3Data;
})
},
{
name: "partition",
inputs: ["d3"],
value: (function(d3){
// console.log('partition',d3);
return(
data => {
const root = d3.hierarchy(data)
.sum(d => d.size)
.sort((a, b) => b.value - a.value);
console.log('data',d3.partition().size([2 * Math.PI, root.height+1])(root));
return d3.partition()
.size([2 * Math.PI, root.height+1])
(root);
}
)
})
},
{
name: "color",
inputs: ["d3","data"],
value: (function(d3,data){return (
d3.scaleOrdinal().range(d3.quantize(d3.interpolateRainbow, data.children.length + 1))
)})
},
{
name: "format",
inputs: ["d3"],
value: (function(d3){return(
d3.format(",d")
)})
},
{
name: "width",
value: (function(){return(
932
)})
},
{
name: "radius",
inputs: ["width"],
value: (function(width){return(
width / 6
)})
},
{
name: "arc",
inputs: ["d3","radius"],
value: (function(d3,radius){return(
d3.arc()
.startAngle(d => d.x0)
.endAngle(d => d.x1)
.padAngle(d => Math.min((d.x1 - d.x0) / 2, 0.005))
.padRadius(radius * 1.5)
.innerRadius(d => d.y0 * radius)
.outerRadius(d => Math.max(d.y0 * radius, d.y1 * radius - 1))
)})
},
{
name: "d3",
inputs: ["require"],
value: (function(require){return(
require("https://d3js.org/d3.v5.min.js")
)})
}
]
};
const notebook = {
id: "123",
modules: [m0]
};
function loadData(){
$.ajax({
type:'GET',
url:'http://shopnz.in/wp-admin/admin-ajax.php?action=wp_ajax_list_items&sunburst=sunburstcat',
success: function(response){
console.log('response',response[0]);
console.log('data-=-=',data);
data = response[0];
}
});
}
export default notebook;
[{"name":"Balance","term_id":"588","slug":"balance","parent":"0","has_children":true,"color":"#aac62d","children":[{"name":"Aroma","term_id":"589","slug":"aroma","parent":"588","has_children":true,"color":"#aac62d","children":[{"name":"Earth","term_id":"593","slug":"earth","parent":"589","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Fruit","term_id":"594","slug":"fruit","parent":"589","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Herb","term_id":"595","slug":"herb","parent":"589","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Pungent","term_id":"596","slug":"pungent","parent":"589","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Spice","term_id":"597","slug":"spice","parent":"589","has_children":false,"size":"1000","color":"#aac62d"}]},{"name":"Health","term_id":"590","slug":"health","parent":"588","has_children":true,"color":"#aac62d","children":[{"name":"Body","term_id":"598","slug":"body","parent":"590","has_children":true,"color":"#aac62d","children":[{"name":"Fatigue","term_id":"613","slug":"fatigue","parent":"598","has_children":true,"color":"#aac62d","children":[{"name":"Sleep","term_id":"616","slug":"sleep","parent":"613","has_children":false,"size":"1000","color":"#aac62d"}]},{"name":"PainRelief","term_id":"614","slug":"painrelief","parent":"598","has_children":true,"color":"#aac62d","children":[{"name":"Spasms","term_id":"617","slug":"spasms","parent":"614","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Relaxed","term_id":"618","slug":"relaxed","parent":"614","has_children":false,"size":"1000","color":"#aac62d"}]},{"name":"Digestion","term_id":"615","slug":"digestion","parent":"598","has_children":true,"color":"#aac62d","children":[{"name":"Appetite","term_id":"619","slug":"appetite","parent":"615","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Nausea","term_id":"620","slug":"nausea","parent":"615","has_children":false,"size":"1000","color":"#aac62d"}]}]},{"name":"Mind","term_id":"599","slug":"mind","parent":"590","has_children":true,"color":"#aac62d","children":[{"name":"Behavior","term_id":"621","slug":"behavior","parent":"599","has_children":true,"color":"#aac62d","children":[{"name":"Sedative","term_id":"623","slug":"sedative","parent":"621","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Energetic","term_id":"624","slug":"energetic","parent":"621","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Focus","term_id":"625","slug":"focus","parent":"621","has_children":false,"size":"1000","color":"#aac62d"}]},{"name":"Mood","term_id":"622","slug":"mood","parent":"599","has_children":true,"color":"#aac62d","children":[{"name":"Creative","term_id":"626","slug":"creative","parent":"622","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Uplifted","term_id":"627","slug":"uplifted","parent":"622","has_children":false,"size":"1000","color":"#aac62d"}]}]}]},{"name":"Medical","term_id":"591","slug":"medical","parent":"588","has_children":true,"color":"#aac62d","children":[{"name":"Condition","term_id":"600","slug":"condition","parent":"591","has_children":true,"color":"#aac62d","children":[{"name":"Cancer","term_id":"602","slug":"cancer","parent":"600","has_children":false,"size":"1000","color":"#aac62d"},{"name":"PMS","term_id":"603","slug":"pms","parent":"600","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Arthritis","term_id":"628","slug":"arthritis","parent":"600","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Asthma","term_id":"629","slug":"asthma","parent":"600","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Cachexia","term_id":"630","slug":"cachexia","parent":"600","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Crohn\u2019s Disease","term_id":"631","slug":"crohns-disease","parent":"600","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Depression (currently a 'symptom')","term_id":"632","slug":"depression-currently-a-symptom","parent":"600","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Epilepsy","term_id":"633","slug":"epilepsy","parent":"600","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Gastrointestinal Disorder","term_id":"634","slug":"gastrointestinal-disorder","parent":"600","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Hypertension","term_id":"635","slug":"hypertension","parent":"600","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Parkinson\u2019s","term_id":"636","slug":"parkinsons","parent":"600","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Phantom Limb Pain","term_id":"637","slug":"phantom-limb-pain","parent":"600","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Spinal Cord Injury","term_id":"638","slug":"spinal-cord-injury","parent":"600","has_children":false,"size":"1000","color":"#aac62d"}]},{"name":"Symptom","term_id":"601","slug":"symptom","parent":"591","has_children":true,"color":"#aac62d","children":[{"name":"Anxiety","term_id":"604","slug":"anxiety","parent":"601","has_children":true,"color":"#aac62d","children":[{"name":"ACDC","term_id":"612","slug":"acdc","parent":"604","has_children":false,"size":"1000","color":"#aac62d"}]},{"name":"Depresssion","term_id":"605","slug":"depresssion","parent":"601","has_children":false,"size":"1000","color":"#aac62d"}]}]},{"name":"Terpenes","term_id":"592","slug":"terpenes","parent":"588","has_children":true,"color":"#aac62d","children":[{"name":"Humulene","term_id":"606","slug":"humulene","parent":"592","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Limonene","term_id":"607","slug":"limonene","parent":"592","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Linalool","term_id":"608","slug":"linalool","parent":"592","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Myrcene","term_id":"609","slug":"myrcene","parent":"592","has_children":false,"size":"1000","color":"#aac62d"},{"name":"Pinene","term_id":"610","slug":"pinene","parent":"592","has_children":true,"color":"#aac62d","children":[{"name":"Romulan","term_id":"611","slug":"romulan","parent":"610","has_children":false,"size":"1000","color":"#aac62d"}]}]}]}]
This is my data.
My goal is that given a value in seconds(resp_time), I want to create a counter in anticlock direction that would end once resp_time becomes 0.
I am following this tutorial: http://bl.ocks.org/mbostock/1096355 to create a polar clock. But I need the arc to decrease as in go anti-clockwise. I tried updating the endAngle value for the same, but it doesn't seem to work.
Here's my code:
var width = 960,
height = 800,
radius = Math.min(width, height) / 1.9,
spacing = .09;
var resp_time = 61;
var rh = parseInt(resp_time/3600), rm = parseInt((resp_time- rh*3600)/60), rs = parseInt((resp_time- rh*3600 - rm*60)%60);
var color = d3.scale.linear()
.range(["hsl(-180,50%,50%)", "hsl(180,50%,50%)"])
.interpolate(interpolateHsl);
var t;
var arc = d3.svg.arc()
.startAngle(0)
.endAngle(function(d) { return d.value * 2 * Math.PI; })
.innerRadius(function(d) { return d.index * radius; })
.outerRadius(function(d) { return (d.index + spacing) * radius; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var field = svg.selectAll("g")
.data(fields)
.enter().append("g");
field.append("path");
field.append("text");
d3.transition().duration(0).each(tick);
d3.select(self.frameElement).style("height", height + "px");
function tick() {
field = field
.each(function(d) { this._value = d.value; })
.data(fields)
.each(function(d) { d.previousValue = this._value; });
field.select("path")
.transition()
.ease("elastic")
.attrTween("d", arcTween)
.style("fill", function(d) { return color(d.value); });
field.select("text")
.attr("dy", function(d) { return d.value < .5 ? "-.5em" : "1em"; })
.text(function(d) { return d.text; })
.transition()
.ease("elastic")
.attr("transform", function(d) {
return "rotate(" + 360 * d.value + ")"
+ "translate(0," + -(d.index + spacing / 2) * radius + ")"
+ "rotate(" + (d.value < .5 ? -90 : 90) + ")"
});
if (resp_time > 0)
{
resp_time = resp_time - 1;
rh = parseInt(resp_time/3600), rm = parseInt((resp_time- rh*3600)/60), rs = parseInt((resp_time- rh*3600 - rm*60)%60);
t = setTimeout(tick, 1000);
}
}
function arcTween(d) {
console.log(d);
var i = d3.interpolateNumber(d.previousValue, d.value);
return function(t) { d.value = i(t); return arc(d); };
}
function fields() {
console.log(rs);
return [
{index: .3, text: rs+"s", value: rs},
{index: .2, text: rm+"m", value: rm},
{index: .1, text: rh+"h", value: rh}
];
}
function interpolateHsl(a, b) {
var i = d3.interpolateString(a, b);
return function(t) {
return d3.hsl(i(t));
};
}
This is just resulting in 3 static concentric circles(since I'm plotting only the minute, seconds and hours) with no transition.
I'm not sure how to proceed from here. What change should I make to get it working? Please help me out.
d.value in this code should be in the range [0, 1], so you need to divide by the maximum values in the fields generator:
function fields() {
console.log(rs);
return [
{index: .3, text: rs+"s", value: rs/60},
{index: .2, text: rm+"m", value: rm/60},
{index: .1, text: rh+"h", value: rh/24}
];
}
I noticed this because console.log(rs) was printing out values in the range of [0, 60] whereas arc.endAngle expects a radian value. If you substitute [0, 60] with the endAngle provider in the code, you get an over wound clock hand.
var arc = d3.svg.arc()
.startAngle(0)
.endAngle(function(d) { return d.value * 2 * Math.PI; })
var width = 960,
height = 800,
radius = Math.min(width, height) / 1.9,
spacing = .09;
var resp_time = 61;
var rh = parseInt(resp_time/3600), rm = parseInt((resp_time- rh*3600)/60), rs = parseInt((resp_time- rh*3600 - rm*60)%60);
var color = d3.scale.linear()
.range(["hsl(-180,50%,50%)", "hsl(180,50%,50%)"])
.interpolate(interpolateHsl);
var t;
var arc = d3.svg.arc()
.startAngle(0)
.endAngle(function(d) { return d.value * 2 * Math.PI; })
.innerRadius(function(d) { return d.index * radius; })
.outerRadius(function(d) { return (d.index + spacing) * radius; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var field = svg.selectAll("g")
.data(fields)
.enter().append("g");
field.append("path");
field.append("text");
d3.transition().duration(0).each(tick);
d3.select(self.frameElement).style("height", height + "px");
function tick() {
field = field
.each(function(d) { this._value = d.value; })
.data(fields)
.each(function(d) { d.previousValue = this._value; });
field.select("path")
.transition()
.ease("elastic")
.attrTween("d", arcTween)
.style("fill", function(d) { return color(d.value); });
field.select("text")
.attr("dy", function(d) { return d.value < .5 ? "-.5em" : "1em"; })
.text(function(d) { return d.text; })
.transition()
.ease("elastic")
.attr("transform", function(d) {
return "rotate(" + 360 * d.value + ")"
+ "translate(0," + -(d.index + spacing / 2) * radius + ")"
+ "rotate(" + (d.value < .5 ? -90 : 90) + ")"
});
if (resp_time > 0)
{
resp_time = resp_time - 1;
rh = parseInt(resp_time/3600), rm = parseInt((resp_time- rh*3600)/60), rs = parseInt((resp_time- rh*3600 - rm*60)%60);
t = setTimeout(tick, 1000);
}
}
function arcTween(d) {
console.log(d);
var i = d3.interpolateNumber(d.previousValue, d.value);
return function(t) { d.value = i(t); return arc(d); };
}
function fields() {
console.log(rs);
return [
{index: .3, text: rs+"s", value: rs/60},
{index: .2, text: rm+"m", value: rm/60},
{index: .1, text: rh+"h", value: rh/24}
];
}
function interpolateHsl(a, b) {
var i = d3.interpolateString(a, b);
return function(t) {
return d3.hsl(i(t));
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I've created this arc chart. I'd like to animate the labels better, have them tween with the arc animations. I've placed the labels inside to avoid being covered up.
jsFiddle
var arcGenerator = {
radius: 100,
oldData: "",
init: function(data){
var clone = jQuery.extend(true, {}, data);
this.oldData = this.setData(clone, false);
this.setup(this.setData(data, true));
},
update: function(data){
var clone = jQuery.extend(true, {}, data);
this.animate(this.setData(data, true));
this.oldData = this.setData(clone, false);
},
animate: function(data){
var that = this;
var chart = d3.select(".arcchart");
that.generateArcs(chart, data);
},
setData: function(data, isSorted){
var diameter = 2 * Math.PI * this.radius;
var localData = new Array();
var displacement = 0;
var oldBatchLength = 0;
$.each(data, function(index, value) {
var riseLevels = value.segments;
var riseLevelCount = riseLevels.length;
if(oldBatchLength !=undefined){
displacement+=oldBatchLength;
}
var arcBatchLength = 2*Math.PI;
var arcPartition = arcBatchLength/riseLevelCount;
$.each(riseLevels, function( ri, value ) {
var startAngle = (ri*arcPartition);
var endAngle = ((ri+1)*arcPartition);
if(index!=0){
startAngle+=displacement;
endAngle+=displacement;
}
riseLevels[ri]["startAngle"] = startAngle;
riseLevels[ri]["endAngle"] = endAngle;
});
oldBatchLength = arcBatchLength;
localData.push(riseLevels);
});
var finalArray = new Array();
$.each(localData, function(index, value) {
$.each(localData[index], function(i, v) {
finalArray.push(v);
});
});
return finalArray;
},
generateArcs: function(chart, data){
var that = this;
//_arc paths
//append previous value to it.
$.each(data, function(index, value) {
if(that.oldData[index] != undefined){
data[index]["previousEndAngle"] = that.oldData[index].endAngle;
}
else{
data[index]["previousEndAngle"] = 0;
}
});
var arcpaths = that.arcpaths.selectAll("path")
.data(data);
arcpaths.enter().append("svg:path")
.attr("class", function(d, i){
return d.machineType;
})
.style("fill", function(d, i){
return d.color;
})
.transition()
.ease("elastic")
.duration(750)
.attrTween("d", arcTween);
arcpaths.transition()
.ease("elastic")
.style("fill", function(d, i){
return d.color;
})
.duration(750)
.attrTween("d",arcTween);
arcpaths.exit().transition()
.ease("bounce")
.duration(750)
.attrTween("d", arcTween)
.remove();
function arcTween(b) {
var prev = JSON.parse(JSON.stringify(b));
prev.endAngle = b.previousEndAngle;
var i = d3.interpolate(prev, b);
return function(t) {
return that.getArc()(i(t));
};
}
//_arc paths
var r = that.radius - 50;
var ir = that.radius + 90;
//__labels
var labels = that.labels.selectAll("text")
.data(data);
labels.enter()
.append("text")
.attr("text-anchor", "middle")
labels
.attr("x", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cx = Math.cos(a) * (ir+((r-ir)/2));
return d.x = Math.cos(a) * (r + 20);
})
.attr("y", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cy = Math.sin(a) * (ir+((r-ir)/2));
return d.y = Math.sin(a) * (r + 20);
})
.text(function(d) {
return d.color;
})
.each(function(d) {
var bbox = this.getBBox();
d.sx = d.x - bbox.width/2 - 2;
d.ox = d.x + bbox.width/2 + 2;
d.sy = d.oy = d.y + 5;
})
.transition()
.duration(300)
labels
.transition()
.duration(300)
labels.exit().remove();
//__labels
//__pointers
that.pointers.append("defs").append("marker")
.attr("id", "circ")
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 3)
.attr("refY", 3)
.append("circle")
.attr("cx", 3)
.attr("cy", 3)
.attr("r", 3);
var pointers = that.pointers.selectAll("path.pointer")
.data(data);
pointers.enter()
.append("path")
.attr("class", "pointer")
.style("fill", "none")
.style("stroke", "black")
.attr("marker-end", "url(#circ)");
pointers
.attr("d", function(d) {
if(d.cx > d.ox) {
return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy;
} else {
return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy;
}
})
.transition()
.duration(300)
pointers
.transition()
.duration(300)
pointers.exit().remove();
//__pointers
},
setup: function(data){
var chart = d3.select("#threshold").append("svg:svg")
.attr("class", "chart")
.attr("width", 420)
.attr("height", 420)
.append("svg:g")
.attr("class", "arcchart")
.attr("transform", "translate(200,200)");
this.arcpaths = chart.append("g")
.attr("class", "arcpaths");
this.labels = chart.append("g")
.attr("class", "labels");
this.pointers = chart.append("g")
.attr("class", "pointer");
this.generateArcs(chart, data);
},
getArc: function(){
var that = this;
var arc = d3.svg.arc()
.innerRadius(function(d, i){
return that.radius;
})
.outerRadius(function(d){
var maxHeight = 100;
var ratio = (d.height/maxHeight * 100)+that.radius;
return ratio;
})
.startAngle(function(d, i){
return d.startAngle;
})
.endAngle(function(d, i){
return d.endAngle;
});
return arc;
}
}
$(document).ready(function() {
var dataCharts = [
{
"data": [
{
"segments": [
{
height: 10,
color: "grey"
},
{
height: 40,
color: "darkgrey"
},
{
height: 33,
color: "grey"
},
{
height: 50,
color: "darkgrey"
},
{
height: 33,
color: "grey"
},
{
height: 10,
color: "darkgrey"
},
{
height: 50,
color: "grey"
},
{
height: 45,
color: "darkgrey"
},
{
height: 10,
color: "grey"
},
{
height: 40,
color: "darkgrey"
}
]
}
]
},
{
"data": [
{
"segments": [
{
height: 50,
color: "red"
},
{
height: 100,
color: "yellow"
},
{
height: 10,
color: "green"
}
]
}
]
}
];
var clone = jQuery.extend(true, {}, dataCharts);
arcGenerator.init(clone[0].data);
$(".testers a").on( "click", function(e) {
e.preventDefault();
var clone = jQuery.extend(true, {}, dataCharts);
var pos = $(this).parent("li").index();
arcGenerator.update(clone[pos].data);
});
});
There are two parts for this. First, the animation of the pointer lines. This is relatively easy and the only thing you're missing is that the .transition() is in the wrong place:
pointers
.transition()
.duration(300)
.attr("d", function(d) {
// etc
The second part is the animation of the text labels. This is a bit more difficult because their computation includes some side effects that allow the correct computation of the pointer lines. This comes in two parts -- the computation of the position and the computation of the extent of the displayed text. With that in mind, the changes are relatively straightforward, we just need to make sure that those computations take place before the transition starts:
labels.text(function(d) {
return d.color;
}).each(function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cx = Math.cos(a) * (ir+((r-ir)/2));
d.cy = Math.sin(a) * (ir+((r-ir)/2));
d.x = d.x || Math.cos(a) * (r + 20);
d.y = d.y || Math.sin(a) * (r + 20);
var bbox = this.getBBox();
d.sx = d.x - bbox.width/2 - 2;
d.ox = d.x + bbox.width/2 + 2;
d.sy = d.oy = d.y + 5;
})
First, the text itself is set. This is required to be able to use .getBBox() to determine its dimensions. Then, all the values required by the pointer paths are computed -- these bits of code were previously in the computation of the position of the text, but that's what we want to transition to so those values are set later (except for new text labels that don't have coordinates set).
All that remains now is to animate the change of coordinates of the text in the same way as before:
.transition()
.duration(300)
.attr("x", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
return d.x = Math.cos(a) * (r + 20);
})
.attr("y", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
return d.y = Math.sin(a) * (r + 20);
});
Complete example here.