Adding Tooltip Functionality to ObservableHQ Plot Locally - javascript

Would there be an easy way to implement this addTooltip function from Mike Freeman's Plot Tooltip Notebook within a local vanilla JavaScript environment? In addition, what would be the best way to manage user input and interactivity with Plot locally? I realize Observable makes all of this a lot less painful to code. Was just hoping there would be solutions outside of the website. Or should I just go the D3.js route if I want to do these things?
<html>
<head>
<meta name=”robots” content=”noindex”>
<meta charset="UTF-8">
<title>Example Plots</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/#observablehq/plot#0.4"></script>
</head>
<body>
<div >
<h1>Iris Dataset</h1>
<div id="chart1"></div>
</div>
</body>
<script type="module">
const iris = await d3.json("https://cdn.jsdelivr.net/npm/vega-datasets#1.31.1/data/iris.json");
const scatter = function(data) {
const div = document.getElementById("chart1")
div.appendChild(Plot.plot({
marks: [
Plot.dot(data, {x: "sepalLength", y: "sepalWidth", stroke: "species"}),
],
}));
}
scatter(iris)
</script>
</html>

As you can see in my example below, you just need to import the htl.html requirement and import (copy and paste) the addTooltips, hover and id_generator cells as functions.
<html>
<head>
<meta name=”robots” content=”noindex”>
<meta charset="UTF-8">
<title>Example Plots</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/#observablehq/plot#0.4"></script>
<script src="https://cdn.jsdelivr.net/npm/htl#0.3.1/dist/htl.min.js"></script>
</head>
<body>
<div >
<h1>Iris Dataset</h1>
<div id="chart1"></div>
</div>
</body>
<script type="module">
const iris = await d3.json("https://cdn.jsdelivr.net/npm/vega-datasets#1.31.1/data/iris.json");
const html = htl.html
function scatter(data) {
const div = document.getElementById("chart1")
div.appendChild(addTooltips(Plot.plot({
marks: [
Plot.dot(data, {x: "sepalLength", y: "sepalWidth", stroke: "species",
title: (d) =>
`${d.species} \n Sepal Length: ${d.sepalLength} \n Sepal Width: ${d.sepalWidth}`
}),
],
})))
}
function addTooltips(chart, hover_styles = { fill: "blue", opacity: 0.5 }){
let styles = hover_styles;
const line_styles = {
stroke: "blue",
"stroke-width": 3
};
// Workaround if it's in a figure
const type = d3.select(chart).node().tagName;
let wrapper =
type === "FIGURE" ? d3.select(chart).select("svg") : d3.select(chart);
// Workaround if there's a legend....
const numSvgs = d3.select(chart).selectAll("svg").size();
if (numSvgs === 2)
wrapper = d3
.select(chart)
.selectAll("svg")
.filter((d, i) => i === 1);
wrapper.style("overflow", "visible"); // to avoid clipping at the edges
// Set pointer events to visibleStroke if the fill is none (e.g., if its a line)
wrapper.selectAll("path").each(function (data, index, nodes) {
// For line charts, set the pointer events to be visible stroke
if (
d3.select(this).attr("fill") === null ||
d3.select(this).attr("fill") === "none"
) {
d3.select(this).style("pointer-events", "visibleStroke");
styles = _.isEqual(hover_styles, { fill: "blue", opacity: 0.5 })
? line_styles
: hover_styles;
}
});
const tip = wrapper
.selectAll(".hover-tip")
.data([""])
.join("g")
.attr("class", "hover")
.style("pointer-events", "none")
.style("text-anchor", "middle");
// Add a unique id to the chart for styling
const id = id_generator();
// Add the event listeners
d3.select(chart)
.classed(id, true) // using a class selector so that it doesn't overwrite the ID
.selectAll("title")
.each(function () {
// Get the text out of the title, set it as an attribute on the parent, and remove it
const title = d3.select(this); // title element that we want to remove
const parent = d3.select(this.parentNode); // visual mark on the screen
const t = title.text();
if (t) {
parent.attr("__title", t).classed("has-title", true);
title.remove();
}
// Mouse events
parent
.on("mousemove", function (event) {
const text = d3.select(this).attr("__title");
const pointer = d3.pointer(event, wrapper.node());
if (text) tip.call(hover, pointer, text.split("\n"));
else tip.selectAll("*").remove();
// Raise it
d3.select(this).raise();
// Keep within the parent horizontally
const tipSize = tip.node().getBBox();
if (pointer[0] + tipSize.x < 0)
tip.attr(
"transform",
`translate(${tipSize.width / 2}, ${pointer[1] + 7})`
);
else if (pointer[0] + tipSize.width / 2 > wrapper.attr("width"))
tip.attr(
"transform",
`translate(${wrapper.attr("width") - tipSize.width / 2}, ${
pointer[1] + 7
})`
);
})
.on("mouseout", function (event) {
tip.selectAll("*").remove();
// Lower it!
d3.select(this).lower();
});
});
// Remove the tip if you tap on the wrapper (for mobile)
wrapper.on("touchstart", () => tip.selectAll("*").remove());
// Add styles
const style_string = Object.keys(styles)
.map((d) => {
return `${d}:${styles[d]};`;
})
.join("");
// Define the styles
const style = html`<style>
.${id} .has-title {
cursor: pointer;
pointer-events: all;
}
.${id} .has-title:hover {
${style_string}
}
</style>`;
chart.appendChild(style);
return chart;
}
function hover(tip, pos, text){
const side_padding = 10;
const vertical_padding = 5;
const vertical_offset = 15;
// Empty it out
tip.selectAll("*").remove();
// Append the text
tip
.style("text-anchor", "middle")
.style("pointer-events", "none")
.attr("transform", `translate(${pos[0]}, ${pos[1] + 7})`)
.selectAll("text")
.data(text)
.join("text")
.style("dominant-baseline", "ideographic")
.text((d) => d)
.attr("y", (d, i) => (i - (text.length - 1)) * 15 - vertical_offset)
.style("font-weight", (d, i) => (i === 0 ? "bold" : "normal"));
const bbox = tip.node().getBBox();
// Add a rectangle (as background)
tip
.append("rect")
.attr("y", bbox.y - vertical_padding)
.attr("x", bbox.x - side_padding)
.attr("width", bbox.width + side_padding * 2)
.attr("height", bbox.height + vertical_padding * 2)
.style("fill", "white")
.style("stroke", "#d3d3d3")
.lower();
}
function id_generator(){
var S4 = function () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return "a" + S4() + S4();
}
scatter(iris)
</script>
</html>

Related

d3 contour Observable to Vanilla JS

I am converting the d3 contour example https://observablehq.com/#d3/volcano-contours?collection=#d3/d3-contour to vanilla js for a stand alone application. I am new to observable and JS.
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<script src="https://d3js.org/d3.v6.min.js"></script>
<script src="https://d3js.org/d3-contour.v2.min.js"></script>
<script src="https://d3js.org/d3-hsv.v0.1.min.js"></script>
</head>
<body>
<div class="container"></div>
<script>
function vChart(container){
const data = FileAttachment("volcano.json").json();
const height = data.height;
const width = data.width;
const contours = d3.contours().size([width, height]);
const path = d3.geoPath();
function interpolateTerrain() {
const i0 = d3.interpolateHsvLong(d3.hsv(120, 1, 0.65), d3.hsv(60, 1, 0.90));
const i1 = d3.interpolateHsvLong(d3.hsv(60, 1, 0.90), d3.hsv(0, 0, 0.95));
return t => t < 0.5 ? i0(t * 2) : i1((t - 0.5) * 2);
}
const color = d3.scaleSequential(interpolateTerrain).domain(d3.extent(data.values)).nice();
const thresholds = color.ticks(20);
const wide = Generators.observe(notify => {
let wide;
function resized() {
let w = innerWidth > 640;
if (w !== wide) notify(wide = w);
}
resized();
addEventListener("resize", resized);
return () => removeEventListener("resize", resized);
})
const svg = d3.select(container).append('svg')
.attr("viewBox", wide ? [0, 0, width, height] : [0, 0, height, width])
.style("display", "block")
.style("margin", "0 -14px")
.style("width", "calc(100% + 28px)")
.style("height", "auto");
const g = svg.append("g")
.attr("transform", wide ? null : `
rotate(90 ${width/2},${height/2})
translate(${(width - height) / 2},${(width - height) / 2})
`)
.attr("stroke", "white")
.attr("stroke-width", 0.03);
for (const threshold of thresholds) {
g.append("path")
.attr("d", path(contours.contour(data.values, threshold)))
.attr("fill", color(threshold));
yield svg.node();
}
}
vChart('.container')
</script>
</body>
</html>
This results in two errors
Uncaught ReferenceError: Fileattachment is not defined
Uncaught ReferenceError: Generators is not defined
I am not able to recreate the generator function, Fileattachment function I guess it is related to observable.

canvg SVG rendering to wrong format

I have a piechart with labels and legend which looks fine in SVG but as soon as I use canvg to transform it to canvas some formats are missing / get wrong.
SVG:
Canvas:
I already inline the CSS to apply all CSS settings but still the format does not match.
Any ideas?
Is this a (canvg) bug or am I doing s.th. wrong?
var columns = ['data11', 'data2', 'data347', 'data40098'];
var data = [150, 250, 300, 50];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;
/**
* global C3Styles object
*/
var C3Styles = null;
var legendData = [];
var sumTotal = 0
//prepare pie data
var columnData = [];
var columnNames = {};
for (i = 0; i < columns.length; i++) {
columnData.push([columns[i]].concat(data[i]));
var val = (Array.isArray(data[i])) ? data[i].reduce(function(pv, cv) {
return pv + cv;
}, 0) : data[i];
sumTotal += val;
legendData.push({
id: columns[i],
value: val,
ratio: 0.0
});
}
legendData.forEach(function(el, i) {
el.ratio = el.value / sumTotal
columnNames[el.id] = [el.id, d3.format(",.0f")(el.value), d3.format(",.1%")(el.ratio)].join(';');
});
var chart = c3.generate({
bindto: d3.select('#chart'),
data: {
columns: [
[columns[0]].concat(data[0])
],
names: columnNames,
type: 'pie',
},
legend: {
position: 'right',
show: true
},
pie: {
label: {
threshold: 0.001,
format: function(value, ratio, id) {
return [id, d3.format(",.0f")(value), "[" + d3.format(",.1%")(ratio) + "]"].join(';');
}
}
},
color: {
pattern: colors
},
onrendered: function() {
redrawLabelBackgrounds();
redrawLegend();
}
});
function addLabelBackground(index) {
/*d3.select('#chart').select("g.c3-target-" + columns[index].replace(/\W+/g, '-')+".c3-chart-arc")
.insert("rect", "text")
.style("fill", colors[index]);*/
var p = d3.select('#chart').select("g.c3-target-" + columns[index].replace(/\W+/g, '-') + ".c3-chart-arc");
var g = p.append("g");
g.append("rect")
.style("fill", colors[index]);
g.append(function() {
return p.select("text").remove().node();
});
}
for (var i = 0; i < columns.length; i++) {
if (i > 0) {
setTimeout(function(column) {
chart.load({
columns: [
columnData[column],
]
});
//chart.data.names(columnNames[column])
addLabelBackground(column);
}, (i * 5000 / columnData.length), i);
} else {
addLabelBackground(i);
}
}
function redrawLegend() {
d3.select('#chart').selectAll(".c3-legend-item > text").each(function() {
// get d3 node
var legendItem = d3.select(this);
var legendItemNode = legendItem.node();
//check if label is drawn
if (legendItemNode) {
if (legendItemNode.childElementCount === 0 && legendItemNode.innerHTML.length > 0) {
//build data
var data = legendItemNode.innerHTML.split(';');
legendItem.text("");
//TODO format legend dynamically depending on text
legendItem.append("tspan")
.text(data[0] + ": ")
.attr("class", "id-row")
.attr("text-anchor", "start");
legendItem.append("tspan")
.text(data[1] + " = ")
.attr("class", "value-row")
.attr("x", 160)
.attr("text-anchor", "end");
legendItem.append("tspan")
.text(data[2])
.attr("class", "ratio-row")
.attr("x", 190)
.attr("text-anchor", "end");
}
}
});
d3.select('#chart').selectAll(".c3-legend-item > rect").each(function() {
var legendItem = d3.select(this);
legendItem.attr("width", 190);
});
}
function redrawLabelBackgrounds() {
//for all label texts drawn yet
//for all label texts drawn yet
d3.select('#chart').selectAll(".c3-chart-arc > g > text").each(function(v) {
// get d3 node
var label = d3.select(this);
var labelNode = label.node();
//check if label is drawn
if (labelNode) {
if (labelNode.childElementCount === 0 && labelNode.innerHTML.length > 0) {
//build data
var data = labelNode.innerHTML.split(';');
label.text("");
data.forEach(function(i, n) {
label.append("tspan")
.text(i)
.attr("dy", (n === 0) ? 0 : "1.2em")
.attr("x", 0)
.attr("text-anchor", "middle");
}, label);
}
//check if element is visible
if (d3.select(labelNode.parentNode).style("display") !== 'none') {
//get pos of the label text
var pos = label.attr("transform").match(/-?\d+(\.\d+)?/g);
if (pos) {
// TODO: mofify the pos of the text
// pos[0] = (pos[0]/h*90000);
// pos[1] = (pos[1]/h*90000);
// remove dy and move label
//d3.select(this).attr("dy", 0);
//d3.select(this).attr("transform", "translate(" + pos[0] + "," + pos[1] + ")");
//get surrounding box of the label
var bbox = labelNode.getBBox();
//now draw and move the rects
d3.select(labelNode.parentNode).select("rect")
.attr("transform", "translate(" + (pos[0] - (bbox.width + padding) / 2) +
"," + (pos[1] - bbox.height / labelNode.childElementCount) + ")")
.attr("width", bbox.width + padding)
.attr("height", bbox.height + padding);
}
}
}
});
}
document.getElementById("exportButton").onclick = function() {
exportChartToImage();
};
function exportChartToImage() {
var createImagePromise = new Promise(function(resolve, reject) {
var images = [];
d3.selectAll('svg').each(function() {
if (this.parentNode) {
images.push(getSvgImage(this.parentNode, true));
}
});
if (images.length > 0)
resolve(images);
else
reject(images);
});
createImagePromise.then(function(images) {
/*images.forEach(function(img, n) {
img.toBlob(function(blob) {
saveAs(blob, "image_" + (n + 1) + ".png");
});
});*/
})
.catch(function(error) {
throw error;
});
};
/**
* Converts a SVG-Chart to a canvas and returns it.
*/
function getSvgImage(svgContainer) {
var svgEl = d3.select(svgContainer).select('svg').node();
var svgCopyEl = svgEl.cloneNode(true);
if (!svgCopyEl)
return;
d3.select("#svgCopyEl").selectAll("*").remove();
d3.select("#svgCopyEl").node().append(svgCopyEl);
//apply all CSS styles to SVG
/* taken from https://gist.github.com/aendrew/1ad2eed6afa29e30d52e#file-exportchart-js
and changed from, angular to D3 functions
*/
/* Take styles from CSS and put as inline SVG attributes so that Canvg
can properly parse them. */
if (!C3Styles) {
var chartStyle;
// Get rules from c3.css
var styleSheets = document.styleSheets;
for (var i = 0; i <= styleSheets.length - 1; i++) {
if (styleSheets[i].href && (styleSheets[i].href.indexOf('c3.min.css') !== -1 || styleSheets[i].href.indexOf('c3.css') !== -1)) {
try {
if (styleSheets[i].rules !== undefined) {
chartStyle = styleSheets[i].rules;
} else {
chartStyle = styleSheets[i].cssRules;
}
break;
}
//Note that SecurityError exception is specific to Firefox.
catch (e) {
if (e.name == 'SecurityError') {
console.log("SecurityError. Cant read: " + styleSheets[i].href);
continue;
}
}
}
if (chartStyle !== null && chartStyle !== undefined) {
C3Styles = {};
var selector;
// Inline apply all the CSS rules as inline
for (i = 0; i < chartStyle.length; i++) {
if (chartStyle[i].type === 1) {
selector = chartStyle[i].selectorText;
var styleDec = chartStyle[i].style;
for (var s = 0; s < styleDec.length; s++) {
C3Styles[styleDec[s]] = styleDec[styleDec[s]];
}
}
}
}
}
}
if (C3Styles) {
d3.select(svgCopyEl).selectAll('.c3:not(.c3-chart):not(path)').style(C3Styles);
}
// SVG doesn't use CSS visibility and opacity is an attribute, not a style property. Change hidden stuff to "display: none"
d3.select(svgCopyEl).selectAll('*')
.filter(function(d) {
return d && d.style && (d.style('visibility') === 'hidden' || d.style('opacity') === '0');
})
.style('display', 'none');
//fix weird back fill
d3.select(svgCopyEl).selectAll("path").attr("fill", "none");
//fix no axes
d3.select(svgCopyEl).selectAll("path.domain").attr("stroke", "black");
//fix no tick
d3.select(svgCopyEl).selectAll(".tick line").attr("stroke", "black");
//apply svg text fill, set color
d3.select(svgCopyEl).selectAll("text:not(.c3-empty):not(.c3-axis)").attr("opacity", 1);
var canvasComputed = d3.select("#canvasComputed").node();
// transform SVG to canvas using external canvg
canvg(canvasComputed, new XMLSerializer().serializeToString(svgCopyEl));
return canvasComputed;
}
.c3-chart-arc.c3-target text {
color: white;
fill: white;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.12/c3.min.css" rel="stylesheet" />
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.12/c3.min.js"></script>
<!-- Required to convert named colors to RGB -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/canvg/1.4/rgbcolor.min.js"></script>
<!-- Optional if you want blur -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/stackblur-canvas/1.4.1/stackblur.min.js"></script>
<!-- Main canvg code -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/canvg/1.5/canvg.js"></script>
<script src="https://fastcdn.org/FileSaver.js/1.1.20151003/FileSaver.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h4>
SVG
</h4>
<div id="chart" class "c3">
</div>
<h4>
copy SVG
</h4>
<div id ="svgCopyEl">
</div>
<div>
<h4>
canvas
</h4>
<canvas id="canvasComputed"></canvas>
</div>
<button type="button" id="exportButton">
export to Canvas
</button>
Basically it was a combination of lack knowledge and a bug.
First of all, all relevant CSS styles have to be inlined when using canvg. Second, there was a bug in canvg using tspans and text-anchors.
Here is the (more or less) working code:
var columns = ['data11', 'data2', 'data347', 'data40098'];
var data = [150, 250, 300, 50];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;
/**
* global C3Styles object
*/
var C3Styles = null;
var legendData = [];
var sumTotal = 0
//prepare pie data
var columnData = [];
var columnNames = {};
for (i = 0; i < columns.length; i++) {
columnData.push([columns[i]].concat(data[i]));
var val = (Array.isArray(data[i])) ? data[i].reduce(function(pv, cv) {
return pv + cv;
}, 0) : data[i];
sumTotal += val;
legendData.push({
id: columns[i],
value: val,
ratio: 0.0
});
}
legendData.forEach(function(el, i) {
el.ratio = el.value / sumTotal
columnNames[el.id] = [el.id, d3.format(",.0f")(el.value), d3.format(",.1%")(el.ratio)].join(';');
});
var chart = c3.generate({
bindto: d3.select('#chart'),
data: {
columns: [
[columns[0]].concat(data[0])
],
names: columnNames,
type: 'pie',
},
legend: {
position: 'right',
show: true
},
pie: {
label: {
threshold: 0.001,
format: function(value, ratio, id) {
return [id, d3.format(",.0f")(value), "[" + d3.format(",.1%")(ratio) + "]"].join(';');
}
}
},
color: {
pattern: colors
},
onrendered: function() {
redrawLabelBackgrounds();
redrawLegend();
}
});
function addLabelBackground(index) {
/*d3.select('#chart').select("g.c3-target-" + columns[index].replace(/\W+/g, '-')+".c3-chart-arc")
.insert("rect", "text")
.style("fill", colors[index]);*/
var p = d3.select('#chart').select("g.c3-target-" + columns[index].replace(/\W+/g, '-') + ".c3-chart-arc");
var g = p.append("g");
g.append("rect")
.style("fill", colors[index]);
g.append(function() {
return p.select("text").remove().node();
});
}
for (var i = 0; i < columns.length; i++) {
if (i > 0) {
setTimeout(function(column) {
chart.load({
columns: [
columnData[column],
]
});
//chart.data.names(columnNames[column])
addLabelBackground(column);
}, (i * 5000 / columnData.length), i);
} else {
addLabelBackground(i);
}
}
function redrawLegend() {
d3.select('#chart').selectAll(".c3-legend-item > text").each(function() {
// get d3 node
var legendItem = d3.select(this);
var legendItemNode = legendItem.node();
//check if label is drawn
if (legendItemNode) {
if (legendItemNode.childElementCount === 0 && legendItemNode.innerHTML.length > 0) {
//build data
var data = legendItemNode.innerHTML.split(';');
legendItem.text("");
//TODO format legend dynamically depending on text
legendItem.append("tspan")
.text(data[0] + ": ")
.attr("class", "id-row")
.attr("text-anchor", "start");
legendItem.append("tspan")
.text(data[1] + " = ")
.attr("class", "value-row")
.attr("x", 160)
.attr("text-anchor", "end");
legendItem.append("tspan")
.text(data[2])
.attr("class", "ratio-row")
.attr("x", 190)
.attr("text-anchor", "end");
}
}
});
d3.select('#chart').selectAll(".c3-legend-item > rect").each(function() {
var legendItem = d3.select(this);
legendItem.attr("width", 190);
});
}
function redrawLabelBackgrounds() {
//for all label texts drawn yet
//for all label texts drawn yet
d3.select('#chart').selectAll(".c3-chart-arc > g > text").each(function(v) {
// get d3 node
var label = d3.select(this);
var labelNode = label.node();
//check if label is drawn
if (labelNode) {
var bbox = labelNode.getBBox();
var labelTextHeight = bbox.height;
if (labelNode.childElementCount === 0 && labelNode.innerHTML.length > 0) {
//build data
var data = labelNode.innerHTML.split(';');
label.html('')
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle");
data.forEach(function(i, n) {
label.append("tspan")
.text(i)
.attr("dy", (n === 0) ? 0 : "1.2em")
.attr("x", 0);
}, label);
}
//check if element is visible
if (d3.select(labelNode.parentNode).style("display") !== 'none') {
//get pos of the label text
var pos = label.attr("transform").match(/-?\d+(\.\d+)?/g);
if (pos) {
// TODO: mofify the pos of the text
// pos[0] = (pos[0]/h*90000);
// pos[1] = (pos[1]/h*90000);
// remove dy and move label
//d3.select(this).attr("dy", 0);
//d3.select(this).attr("transform", "translate(" + pos[0] + "," + pos[1] + ")");
//get surrounding box of the label
bbox = labelNode.getBBox();
//now draw and move the rects
d3.select(labelNode.parentNode).select("rect")
.attr("transform", "translate(" + (pos[0] - bbox.width / 2 - padding) +
"," + (pos[1] - labelTextHeight/2 - padding)+")")
.attr("width", bbox.width + 2*padding)
.attr("height", bbox.height + 2*padding);
}
}
}
});
}
document.getElementById("exportButton").onclick = function() {
exportChartToImage();
};
function exportChartToImage() {
var createImagePromise = new Promise(function(resolve, reject) {
var images = [];
d3.selectAll('svg').each(function() {
if (this.parentNode) {
images.push(getSvgImage(this.parentNode, true));
}
});
if (images.length > 0)
resolve(images);
else
reject(images);
});
createImagePromise.then(function(images) {
/*images.forEach(function(img, n) {
img.toBlob(function(blob) {
saveAs(blob, "image_" + (n + 1) + ".png");
});
});*/
})
.catch(function(error) {
throw error;
});
};
/**
* Converts a SVG-Chart to a canvas and returns it.
*/
function getSvgImage(svgContainer) {
var svgEl = d3.select(svgContainer).select('svg').node();
var svgCopyEl = svgEl.cloneNode(true);
if (!svgCopyEl)
return;
d3.select("#svgCopyEl").selectAll("*").remove();
d3.select("#svgCopyEl").node().append(svgCopyEl); //.transition().duration(0);
//apply C3 CSS styles to SVG
// SVG doesn't use CSS visibility and opacity is an attribute, not a style property. Change hidden stuff to "display: none"
d3.select("#svgCopyEl").selectAll('*')
.filter(function(d) {
return d && d.style && (d.style('visibility') === 'hidden' || d.style('opacity') === '0');
})
.style('display', 'none');
d3.select("#svgCopyEl").selectAll('.c3-chart path')
.filter(function(d) {
return d && d.style('fill') === 'none';
})
.attr('fill', 'none');
d3.select("#svgCopyEl").selectAll('.c3-chart path')
.filter(function(d) {
return d && d.style('fill') !== 'none';
})
.attr('fill', function(d) {
return d.style('fill');
});
//set c3 default font
d3.select("#svgCopyEl").selectAll('.c3 svg')
.style('font', 'sans-serif')
.style('font-size', '10px');
//set c3 legend font
d3.select("#svgCopyEl").selectAll('.c3-legend-item > text')
.style('font', 'sans-serif')
.style('font-size', '12px');
d3.select("#svgCopyEl").selectAll('.c3-legend-item > text > tspan')
.style('font', 'sans-serif')
.style('font-size', '12px');
//set c3 arc shapes
d3.select("#svgCopyEl").selectAll('.c3-chart-arc path,rect')
.style('stroke', '#fff');
d3.select("#svgCopyEl").selectAll('.c3-chart-arc text')
.attr('fill', '#fff')
.style('font', 'sans-serif')
.style('font-size', '13px');
//fix weird back fill
d3.select("#svgCopyEl").selectAll("path").attr("fill", "none");
//fix no axes
d3.select("#svgCopyEl").selectAll("path.domain").attr("stroke", "black");
//fix no tick
d3.select("#svgCopyEl").selectAll(".tick line").attr("stroke", "black");
var canvasComputed = d3.select("#canvasComputed").node();
// transform SVG to canvas using external canvg
canvg(canvasComputed, new XMLSerializer().serializeToString(svgCopyEl));
return canvasComputed;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.12/c3.min.css" rel="stylesheet" />
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.12/c3.min.js"></script> -->
<!-- Required to convert named colors to RGB -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/canvg/1.4/rgbcolor.min.js"></script>
<!-- Optional if you want blur -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/stackblur-canvas/1.4.1/stackblur.min.js"></script>
<!-- Main canvg code -->
<script src="https://cdn.jsdelivr.net/npm/canvg#2.0.0-beta.1/dist/browser/canvg.min.js"></script>
<script src="https://fastcdn.org/FileSaver.js/1.1.20151003/FileSaver.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h4>
SVG
</h4>
<div id="chart" class "c3">
</div>
<h4>
copy SVG
</h4>
<div id="svgCopyEl">
</div>
<div>
<h4>
canvas
</h4>
<canvas id="canvasComputed"></canvas>
</div>
<button type="button" id="exportButton">
export to Canvas
</button>

How to create progress bars?

I want to create a waveform with progress bars that fill with another color from left to right.
Right now it looks:
I want it to look like this. Yellow is buffered audio, orange is playing right now. I already have these values.
The main question is how do I fill each rect with color by currentTime of audio?
Here's my code:
const elementWidth = 1100
const elementHeight = 64
const duration = 160
const currentTime = 20
const buffered = 140
// here's data that i get from web audio api
// bar length in seconds would be:
// duration / renderData = how much seconds in one bar
const renderData = [
[-0.015067690176936956, 0.015065840696712662],
[-0.009963374263646985, 0.009960838406137254],
[-0.0329772714073922, 0.032922178973984494],
[-0.02010780853750818, 0.020192897509204638],
[-0.029141768346505944, 0.02913273608186522],
[-0.03390369982419367, 0.033888949138664096],
[-0.05309944789682607, 0.053106191954295334],
[-0.017992382356680794, 0.0179506794436456],
[-0.04118192967225779, 0.04120773269527067],
[-0.032132343283569134, 0.03223372926977138],
[-0.04340663941189386, 0.043317410948806916],
[-0.026866048759920942, 0.02695383570549558],
[-0.041548487297645216, 0.04142889765358134],
[-0.0512541217270734, 0.05128097373670754],
[-0.02645596673127562, 0.026461825339764114],
[-0.03276659370022165, 0.032869462727325334],
[-0.02983164709570332, 0.02965126735342542],
[-0.06186988270590101, 0.06228762507639405],
[-0.037202475771159274, 0.03684529067849468],
[-0.04496168984286248, 0.044984343262096924],
[-0.02961698097048877, 0.029580527280458145],
[-0.06637895360455075, 0.06584970915134748],
[-0.03966561332234608, 0.04028105442218536],
[-0.04888827685580639, 0.04879637577182824],
[-0.034440279218927505, 0.03448690299802526],
[-0.04076603383847427, 0.04087949817166488],
[-0.03422100968150345, 0.03407137586231854],
[-0.03420552026962888, 0.034233479991186845],
[-0.06124921943975816, 0.06133406711072517],
[-0.08080063612343565, 0.08052139740352077],
[-0.052296123826832304, 0.05245498821828788],
[-0.07728568068325997, 0.0772439557897976],
[-0.04070025960953707, 0.04072465208052425],
[-0.016598400103531252, 0.01673240062886387],
[-0.0495708419979178, 0.04952405213368158],
[-0.03402468183819489, 0.03404496946468417],
[-0.04719791564971553, 0.04716565090961255],
[-0.024305039710776202, 0.024425998358774473],
[-0.04539290174457686, 0.0453603392364138],
[-0.04291280211166326, 0.042803252613569195],
[-0.03237617188947045, 0.032430479168267405],
[-0.046939414609483046, 0.046991124408919255],
[-0.037727014544829074, 0.03756628029896137],
[-0.05813820211592722, 0.058137499737658825],
[-0.03306609736616569, 0.03332803022833292],
[-0.03706343131822335, 0.03699838219166897],
[-0.031640843865570666, 0.03150685332686255],
[-0.07978720110560034, 0.07982405111308474],
[-0.04565408283291298, 0.04548542047551325],
[-0.03838929844552628, 0.0386080775422541],
[-0.0349069030273341, 0.03516624962570975],
[-0.05791808093217102, 0.057646960595115364],
[-0.040111244425499945, 0.040190047578908046],
[-0.0421531094659709, 0.04210734133509555],
[-0.04358563889018587, 0.043380678911277275],
[-0.024025454017633886, 0.024179111399202893],
[-0.039038574013751944, 0.03889745017750074],
[-0.02962543563292595, 0.02975662299643922],
[-0.07215596460653108, 0.07225534620830149],
[-0.0845103969948925, 0.08417566858032748],
[-0.05029865141667644, 0.05110349428845409],
[-0.06766253837563593, 0.06680008803627584],
[-0.05413748268128195, 0.054261121431710246],
[-0.04702217202288801, 0.04710783667779247],
[-0.047177278676382065, 0.047241381909344966],
[-0.04949906253183499, 0.049358880485210296],
[-0.06384145451618915, 0.06398437795989458],
[-0.0532812223855561, 0.05336013656088595],
[-0.055032831282645335, 0.055131815418379866],
[-0.05771727930777607, 0.05743980672281111],
[-0.06865421948220482, 0.06896493506959074],
[-0.05163944571854085, 0.05129081551014095],
[-0.04546664828758613, 0.04549366890782257],
[-0.02196073923070452, 0.022119579288034315],
[-0.026824862238895183, 0.026915318981447094],
[-0.04771898452983383, 0.04768769589918763],
[-0.05221904154341058, 0.05202229643239835],
[-0.04034726803191834, 0.040288317010035164],
[-0.04252634158686052, 0.04275796625513488],
[-0.055381424446109724, 0.05515857756430962],
[-0.06160043085044191, 0.06143890271068376],
[-0.04579617210990365, 0.04612433751815954],
[-0.039244869887493206, 0.03927668403684328],
[-0.03426885260996771, 0.03423936180141113],
[-0.03516869910983574, 0.035127711830890515],
[-0.026964357386084752, 0.02699723933039285],
[-0.03816966714682839, 0.03778890745758835],
[-0.04777519168041681, 0.04824239079542675],
[-0.07617805358108933, 0.07612545525147858],
[-0.047140552370394925, 0.04744151736320112],
[-0.05137018378775051, 0.051114804207469784],
[-0.03259493948312707, 0.0325308332802452],
[-0.05715909221362399, 0.05709963073119724],
[-0.04835633252739353, 0.04849600527981289],
[-0.0433886628912617, 0.04331087342221564],
[-0.05191740499328957, 0.05183144200010501],
[-0.022690824730811025, 0.02281282548488598],
[-0.021657892287654815, 0.02160585204290785],
[-0.019911292276869504, 0.01990373441321122],
[-0.05252214322669061, 0.052514338488489534],
[-0.045757900781809524, 0.04581189437809006],
[-0.02396372548560904, 0.023788207356191405],
[-0.053426097224355276, 0.05348064888976746],
[-0.05394891160261981, 0.05421456735805457],
[-0.05251658416178273, 0.05238904616093791],
[-0.04774168806444406, 0.047755594530669916],
[-0.03506924339896615, 0.035076784816174336],
[-0.044288649573623336, 0.044337743067559894],
[-0.05109649028135573, 0.050986769978167874],
[-0.03986396401411081, 0.03992226520835857],
[-0.06271544843396921, 0.0628629998182233],
[-0.060325113831802425, 0.06014867491287253],
[-0.06409607265208252, 0.06426716029136537],
[-0.02890807357828784, 0.02879981209701445],
[-0.0579076968762734, 0.058055472378755635],
[-0.0788244096514242, 0.07889209396389751],
[-0.05489594835332056, 0.054304463238473114],
[-0.05066376350430718, 0.051136225666937284],
[-0.04324084422009672, 0.043106921303429975],
[-0.03618639085199314, 0.03630391952984575],
[-0.03229893887218463, 0.032254130211298596],
[-0.040388961018727465, 0.04034166483632292],
[-0.06891322548088202, 0.06894551548689337],
[-0.05708462516274434, 0.05713687370165375],
[-0.0908320094478539, 0.09053809343169553],
[-0.06997210675874246, 0.07036387396569341],
[-0.027676689451677956, 0.02757377175784071],
[-0.02882633060378825, 0.029207481257562274],
[-0.0414701765332311, 0.04136630655327525],
[-0.05308296364144847, 0.0526747543606357],
[-0.02724146501450132, 0.027406581699254588],
[-0.04265844625269343, 0.04270290902986972],
[-0.03899306746018118, 0.038745252551468795],
[-0.0552804734553083, 0.05535944558193926],
[-0.02309096284644189, 0.023040044134232315],
[-0.0507964500028555, 0.05096013747702334],
[-0.04123972706510699, 0.041359046982264745],
[-0.03236153261658939, 0.032179960855430505],
[-0.02858521671477931, 0.028570736354436077],
[-0.03515761112679279, 0.03513507691850391],
[-0.049852204843317816, 0.04984858000374448],
[-0.038280519845162314, 0.038365751907998916],
[-0.05489151074836156, 0.054958999808454506],
[-0.02552547302215947, 0.025555844960312334],
[-0.06393766191228746, 0.0638978766928521],
[-0.04140103340243134, 0.04113465467714282],
[-0.04647459357809104, 0.04654619117779597],
[-0.03293849813553063, 0.03301029011724379],
[-0.04428244235309984, 0.04433992273438912],
[-0.047489538949244604, 0.04755256034371833],
[-0.047176763166566854, 0.04719291045558167],
[-0.06353201748860114, 0.06380784207550017],
[-0.07775209195691819, 0.0773872824070752],
[-0.054300174262817344, 0.054476381979975085],
[-0.08808678703605805, 0.0879414485377677],
[-0.04016286323725983, 0.04007725752721749],
[-0.01889086923709467, 0.018989486049242103]
]
const height = d3.scaleLinear()
.domain(d3.extent(renderData.map(e => e[1] - e[0])))
.range([0, elementHeight])
d3.select(document.getElementById('app'))
.append('svg')
.attr('class', 'd3')
.attr('width', elementWidth)
.attr('height', elementHeight)
.selectAll('.bar')
.data(renderData)
.enter()
.append('rect')
.attr('class', 'bar')
.attr('fill', '#E0E0E0')
.attr('x', (d, i) => (i * 2 + i))
.attr('y', d => elementHeight - height(d[1] - d[0]))
.transition()
.duration(300)
.ease(d3.easeLinear)
.attr('width', 2)
.attr('height', d => height(d[1] - d[0]))
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id='app' />
</body>
</html>
Its really fun to play with svg :)
This is what i made, have a look.
const elementWidth = 1100
const elementHeight = 64
var audioTotalTime = 120.0000; // in secound
var currentTime = 0;
var currentBuffer = 0;
const renderData = [
[-0.015067690176936956, 0.015065840696712662],
[-0.009963374263646985, 0.009960838406137254],
[-0.0329772714073922, 0.032922178973984494],
[-0.02010780853750818, 0.020192897509204638],
[-0.029141768346505944, 0.02913273608186522],
[-0.03390369982419367, 0.033888949138664096],
[-0.05309944789682607, 0.053106191954295334],
[-0.017992382356680794, 0.0179506794436456],
[-0.04118192967225779, 0.04120773269527067],
[-0.032132343283569134, 0.03223372926977138],
[-0.04340663941189386, 0.043317410948806916],
[-0.026866048759920942, 0.02695383570549558],
[-0.041548487297645216, 0.04142889765358134],
[-0.0512541217270734, 0.05128097373670754],
[-0.02645596673127562, 0.026461825339764114],
[-0.03276659370022165, 0.032869462727325334],
[-0.02983164709570332, 0.02965126735342542],
[-0.06186988270590101, 0.06228762507639405],
[-0.037202475771159274, 0.03684529067849468],
[-0.04496168984286248, 0.044984343262096924],
[-0.02961698097048877, 0.029580527280458145],
[-0.06637895360455075, 0.06584970915134748],
[-0.03966561332234608, 0.04028105442218536],
[-0.04888827685580639, 0.04879637577182824],
[-0.034440279218927505, 0.03448690299802526],
[-0.04076603383847427, 0.04087949817166488],
[-0.03422100968150345, 0.03407137586231854],
[-0.03420552026962888, 0.034233479991186845],
[-0.06124921943975816, 0.06133406711072517],
[-0.08080063612343565, 0.08052139740352077],
[-0.052296123826832304, 0.05245498821828788],
[-0.07728568068325997, 0.0772439557897976],
[-0.04070025960953707, 0.04072465208052425],
[-0.016598400103531252, 0.01673240062886387],
[-0.0495708419979178, 0.04952405213368158],
[-0.03402468183819489, 0.03404496946468417],
[-0.04719791564971553, 0.04716565090961255],
[-0.024305039710776202, 0.024425998358774473],
[-0.04539290174457686, 0.0453603392364138],
[-0.04291280211166326, 0.042803252613569195],
[-0.03237617188947045, 0.032430479168267405],
[-0.046939414609483046, 0.046991124408919255],
[-0.037727014544829074, 0.03756628029896137],
[-0.05813820211592722, 0.058137499737658825],
[-0.03306609736616569, 0.03332803022833292],
[-0.03706343131822335, 0.03699838219166897],
[-0.031640843865570666, 0.03150685332686255],
[-0.07978720110560034, 0.07982405111308474],
[-0.04565408283291298, 0.04548542047551325],
[-0.03838929844552628, 0.0386080775422541],
[-0.0349069030273341, 0.03516624962570975],
[-0.05791808093217102, 0.057646960595115364],
[-0.040111244425499945, 0.040190047578908046],
[-0.0421531094659709, 0.04210734133509555],
[-0.04358563889018587, 0.043380678911277275],
[-0.024025454017633886, 0.024179111399202893],
[-0.039038574013751944, 0.03889745017750074],
[-0.02962543563292595, 0.02975662299643922],
[-0.07215596460653108, 0.07225534620830149],
[-0.0845103969948925, 0.08417566858032748],
[-0.05029865141667644, 0.05110349428845409],
[-0.06766253837563593, 0.06680008803627584],
[-0.05413748268128195, 0.054261121431710246],
[-0.04702217202288801, 0.04710783667779247],
[-0.047177278676382065, 0.047241381909344966],
[-0.04949906253183499, 0.049358880485210296],
[-0.06384145451618915, 0.06398437795989458],
[-0.0532812223855561, 0.05336013656088595],
[-0.055032831282645335, 0.055131815418379866],
[-0.05771727930777607, 0.05743980672281111],
[-0.06865421948220482, 0.06896493506959074],
[-0.05163944571854085, 0.05129081551014095],
[-0.04546664828758613, 0.04549366890782257],
[-0.02196073923070452, 0.022119579288034315],
[-0.026824862238895183, 0.026915318981447094],
[-0.04771898452983383, 0.04768769589918763],
[-0.05221904154341058, 0.05202229643239835],
[-0.04034726803191834, 0.040288317010035164],
[-0.04252634158686052, 0.04275796625513488],
[-0.055381424446109724, 0.05515857756430962],
[-0.06160043085044191, 0.06143890271068376],
[-0.04579617210990365, 0.04612433751815954],
[-0.039244869887493206, 0.03927668403684328],
[-0.03426885260996771, 0.03423936180141113],
[-0.03516869910983574, 0.035127711830890515],
[-0.026964357386084752, 0.02699723933039285],
[-0.03816966714682839, 0.03778890745758835],
[-0.04777519168041681, 0.04824239079542675],
[-0.07617805358108933, 0.07612545525147858],
[-0.047140552370394925, 0.04744151736320112],
[-0.05137018378775051, 0.051114804207469784],
[-0.03259493948312707, 0.0325308332802452],
[-0.05715909221362399, 0.05709963073119724],
[-0.04835633252739353, 0.04849600527981289],
[-0.0433886628912617, 0.04331087342221564],
[-0.05191740499328957, 0.05183144200010501],
[-0.022690824730811025, 0.02281282548488598],
[-0.021657892287654815, 0.02160585204290785],
[-0.019911292276869504, 0.01990373441321122],
[-0.05252214322669061, 0.052514338488489534],
[-0.045757900781809524, 0.04581189437809006],
[-0.02396372548560904, 0.023788207356191405],
[-0.053426097224355276, 0.05348064888976746],
[-0.05394891160261981, 0.05421456735805457],
[-0.05251658416178273, 0.05238904616093791],
[-0.04774168806444406, 0.047755594530669916],
[-0.03506924339896615, 0.035076784816174336],
[-0.044288649573623336, 0.044337743067559894],
[-0.05109649028135573, 0.050986769978167874],
[-0.03986396401411081, 0.03992226520835857],
[-0.06271544843396921, 0.0628629998182233],
[-0.060325113831802425, 0.06014867491287253],
[-0.06409607265208252, 0.06426716029136537],
[-0.02890807357828784, 0.02879981209701445],
[-0.0579076968762734, 0.058055472378755635],
[-0.0788244096514242, 0.07889209396389751],
[-0.05489594835332056, 0.054304463238473114],
[-0.05066376350430718, 0.051136225666937284],
[-0.04324084422009672, 0.043106921303429975],
[-0.03618639085199314, 0.03630391952984575],
[-0.03229893887218463, 0.032254130211298596],
[-0.040388961018727465, 0.04034166483632292],
[-0.06891322548088202, 0.06894551548689337],
[-0.05708462516274434, 0.05713687370165375],
[-0.0908320094478539, 0.09053809343169553],
[-0.06997210675874246, 0.07036387396569341],
[-0.027676689451677956, 0.02757377175784071],
[-0.02882633060378825, 0.029207481257562274],
[-0.0414701765332311, 0.04136630655327525],
[-0.05308296364144847, 0.0526747543606357],
[-0.02724146501450132, 0.027406581699254588],
[-0.04265844625269343, 0.04270290902986972],
[-0.03899306746018118, 0.038745252551468795],
[-0.0552804734553083, 0.05535944558193926],
[-0.02309096284644189, 0.023040044134232315],
[-0.0507964500028555, 0.05096013747702334],
[-0.04123972706510699, 0.041359046982264745],
[-0.03236153261658939, 0.032179960855430505],
[-0.02858521671477931, 0.028570736354436077],
[-0.03515761112679279, 0.03513507691850391],
[-0.049852204843317816, 0.04984858000374448],
[-0.038280519845162314, 0.038365751907998916],
[-0.05489151074836156, 0.054958999808454506],
[-0.02552547302215947, 0.025555844960312334],
[-0.06393766191228746, 0.0638978766928521],
[-0.04140103340243134, 0.04113465467714282],
[-0.04647459357809104, 0.04654619117779597],
[-0.03293849813553063, 0.03301029011724379],
[-0.04428244235309984, 0.04433992273438912],
[-0.047489538949244604, 0.04755256034371833],
[-0.047176763166566854, 0.04719291045558167],
[-0.06353201748860114, 0.06380784207550017],
[-0.07775209195691819, 0.0773872824070752],
[-0.054300174262817344, 0.054476381979975085],
[-0.08808678703605805, 0.0879414485377677],
[-0.04016286323725983, 0.04007725752721749],
[-0.01889086923709467, 0.018989486049242103]
]
const height = d3.scaleLinear()
.domain(d3.extent(renderData.map(e => e[1] - e[0])))
.range([0, elementHeight])
d3.select(document.getElementById('app'))
.append('svg')
.attr('class', 'd3')
.attr('width', elementWidth)
.attr('height', elementHeight)
.selectAll('.bar')
.data(renderData)
.enter()
.append('rect')
.attr('class', 'bar')
.attr('fill', '#E0E0E0')
.attr('x', (d, i) => (i * 2 + i))
.attr('y', d => elementHeight - height(d[1] - d[0]))
.transition()
.duration(300)
.ease(d3.easeLinear)
.attr('width', 2)
.attr('height', d => height(d[1] - d[0]))
var svg = $(".d3");
var lng = svg.find("rect").length;
function update(){
var selectedRect = Math.floor(((currentTime ) / ( lng )) * (audioTotalTime + 86));
var selectedBufferRect = Math.floor(((currentBuffer ) / ( lng )) * (audioTotalTime + 86));
// this is the best i could do, but you understand the ide
var playingColor = "red";
var bufferColor = "green";
// buffer Progress
$.each (svg.find("rect"), function(index, i){ // buffer
if (index<= selectedBufferRect &&$(this).attr("fill") != playingColor )
$(this).attr("fill", bufferColor);
});
// Playing Progress
$.each (svg.find("rect"), function(index, i){ // Playing
if (index<= selectedRect)
$(this).attr("fill", playingColor);
});
}
function PlayingSimulator(){
currentTime += 1
currentBuffer +=3;
if (currentTime>= audioTotalTime){
update();
return false;
}
update();
setTimeout(PlayingSimulator, 60);
}
PlayingSimulator();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id='app' />
</body>
</html>
Just add some simple logic in function paint() to determine the fill color. Then you need need to decide how often to re-render (I guess at least once every second for updating currentTime).
const elementWidth = 1100;
const elementHeight = 64;
const duration = 160;
let currentTime = 0;
const buffered = 140;
const renderData = [[-0.015067690176936956, 0.015065840696712662],[-0.009963374263646985, 0.009960838406137254],[-0.0329772714073922, 0.032922178973984494],[-0.02010780853750818, 0.020192897509204638],[-0.029141768346505944, 0.02913273608186522],[-0.03390369982419367, 0.033888949138664096],[-0.05309944789682607, 0.053106191954295334],[-0.017992382356680794, 0.0179506794436456],[-0.04118192967225779, 0.04120773269527067],[-0.032132343283569134, 0.03223372926977138],[-0.04340663941189386, 0.043317410948806916],[-0.026866048759920942, 0.02695383570549558],[-0.041548487297645216, 0.04142889765358134],[-0.0512541217270734, 0.05128097373670754],[-0.02645596673127562, 0.026461825339764114],[-0.03276659370022165, 0.032869462727325334],[-0.02983164709570332, 0.02965126735342542],[-0.06186988270590101, 0.06228762507639405],[-0.037202475771159274, 0.03684529067849468],[-0.04496168984286248, 0.044984343262096924],[-0.02961698097048877, 0.029580527280458145],[-0.06637895360455075, 0.06584970915134748],[-0.03966561332234608, 0.04028105442218536],[-0.04888827685580639, 0.04879637577182824],[-0.034440279218927505, 0.03448690299802526],[-0.04076603383847427, 0.04087949817166488],[-0.03422100968150345, 0.03407137586231854],[-0.03420552026962888, 0.034233479991186845],[-0.06124921943975816, 0.06133406711072517],[-0.08080063612343565, 0.08052139740352077],[-0.052296123826832304, 0.05245498821828788],[-0.07728568068325997, 0.0772439557897976],[-0.04070025960953707, 0.04072465208052425],[-0.016598400103531252, 0.01673240062886387],[-0.0495708419979178, 0.04952405213368158],[-0.03402468183819489, 0.03404496946468417],[-0.04719791564971553, 0.04716565090961255],[-0.024305039710776202, 0.024425998358774473],[-0.04539290174457686, 0.0453603392364138],[-0.04291280211166326, 0.042803252613569195],[-0.03237617188947045, 0.032430479168267405],[-0.046939414609483046, 0.046991124408919255],[-0.037727014544829074, 0.03756628029896137],[-0.05813820211592722, 0.058137499737658825],[-0.03306609736616569, 0.03332803022833292],[-0.03706343131822335, 0.03699838219166897],[-0.031640843865570666, 0.03150685332686255],[-0.07978720110560034, 0.07982405111308474],[-0.04565408283291298, 0.04548542047551325],[-0.03838929844552628, 0.0386080775422541],[-0.0349069030273341, 0.03516624962570975],[-0.05791808093217102, 0.057646960595115364],[-0.040111244425499945, 0.040190047578908046],[-0.0421531094659709, 0.04210734133509555],[-0.04358563889018587, 0.043380678911277275],[-0.024025454017633886, 0.024179111399202893],[-0.039038574013751944, 0.03889745017750074],[-0.02962543563292595, 0.02975662299643922],[-0.07215596460653108, 0.07225534620830149],[-0.0845103969948925, 0.08417566858032748],[-0.05029865141667644, 0.05110349428845409],[-0.06766253837563593, 0.06680008803627584],[-0.05413748268128195, 0.054261121431710246],[-0.04702217202288801, 0.04710783667779247],[-0.047177278676382065, 0.047241381909344966],[-0.04949906253183499, 0.049358880485210296],[-0.06384145451618915, 0.06398437795989458],[-0.0532812223855561, 0.05336013656088595],[-0.055032831282645335, 0.055131815418379866],[-0.05771727930777607, 0.05743980672281111],[-0.06865421948220482, 0.06896493506959074],[-0.05163944571854085, 0.05129081551014095],[-0.04546664828758613, 0.04549366890782257],[-0.02196073923070452, 0.022119579288034315],[-0.026824862238895183, 0.026915318981447094],[-0.04771898452983383, 0.04768769589918763],[-0.05221904154341058, 0.05202229643239835],[-0.04034726803191834, 0.040288317010035164],[-0.04252634158686052, 0.04275796625513488],[-0.055381424446109724, 0.05515857756430962],[-0.06160043085044191, 0.06143890271068376],[-0.04579617210990365, 0.04612433751815954],[-0.039244869887493206, 0.03927668403684328],[-0.03426885260996771, 0.03423936180141113],[-0.03516869910983574, 0.035127711830890515],[-0.026964357386084752, 0.02699723933039285],[-0.03816966714682839, 0.03778890745758835],[-0.04777519168041681, 0.04824239079542675],[-0.07617805358108933, 0.07612545525147858],[-0.047140552370394925, 0.04744151736320112],[-0.05137018378775051, 0.051114804207469784],[-0.03259493948312707, 0.0325308332802452],[-0.05715909221362399, 0.05709963073119724],[-0.04835633252739353, 0.04849600527981289],[-0.0433886628912617, 0.04331087342221564],[-0.05191740499328957, 0.05183144200010501],[-0.022690824730811025, 0.02281282548488598],[-0.021657892287654815, 0.02160585204290785],[-0.019911292276869504, 0.01990373441321122],[-0.05252214322669061, 0.052514338488489534],[-0.045757900781809524, 0.04581189437809006],[-0.02396372548560904, 0.023788207356191405],[-0.053426097224355276, 0.05348064888976746],[-0.05394891160261981, 0.05421456735805457],[-0.05251658416178273, 0.05238904616093791],[-0.04774168806444406, 0.047755594530669916],[-0.03506924339896615, 0.035076784816174336],[-0.044288649573623336, 0.044337743067559894],[-0.05109649028135573, 0.050986769978167874],[-0.03986396401411081, 0.03992226520835857],[-0.06271544843396921, 0.0628629998182233],[-0.060325113831802425, 0.06014867491287253],[-0.06409607265208252, 0.06426716029136537],[-0.02890807357828784, 0.02879981209701445],[-0.0579076968762734, 0.058055472378755635],[-0.0788244096514242, 0.07889209396389751],[-0.05489594835332056, 0.054304463238473114],[-0.05066376350430718, 0.051136225666937284],[-0.04324084422009672, 0.043106921303429975],[-0.03618639085199314, 0.03630391952984575],[-0.03229893887218463, 0.032254130211298596],[-0.040388961018727465, 0.04034166483632292],[-0.06891322548088202, 0.06894551548689337],[-0.05708462516274434, 0.05713687370165375],[-0.0908320094478539, 0.09053809343169553],[-0.06997210675874246, 0.07036387396569341],[-0.027676689451677956, 0.02757377175784071],[-0.02882633060378825, 0.029207481257562274],[-0.0414701765332311, 0.04136630655327525],[-0.05308296364144847, 0.0526747543606357],[-0.02724146501450132, 0.027406581699254588],[-0.04265844625269343, 0.04270290902986972],[-0.03899306746018118, 0.038745252551468795],[-0.0552804734553083, 0.05535944558193926],[-0.02309096284644189, 0.023040044134232315],[-0.0507964500028555, 0.05096013747702334],[-0.04123972706510699, 0.041359046982264745],[-0.03236153261658939, 0.032179960855430505],[-0.02858521671477931, 0.028570736354436077],[-0.03515761112679279, 0.03513507691850391],[-0.049852204843317816, 0.04984858000374448],[-0.038280519845162314, 0.038365751907998916],[-0.05489151074836156, 0.054958999808454506],[-0.02552547302215947, 0.025555844960312334],[-0.06393766191228746, 0.0638978766928521],[-0.04140103340243134, 0.04113465467714282],[-0.04647459357809104, 0.04654619117779597],[-0.03293849813553063, 0.03301029011724379],[-0.04428244235309984, 0.04433992273438912],[-0.047489538949244604, 0.04755256034371833],[-0.047176763166566854, 0.04719291045558167],[-0.06353201748860114, 0.06380784207550017],[-0.07775209195691819, 0.0773872824070752],[-0.054300174262817344, 0.054476381979975085],[-0.08808678703605805, 0.0879414485377677],[-0.04016286323725983, 0.04007725752721749],[-0.01889086923709467, 0.018989486049242103]];
const height = d3
.scaleLinear()
.domain(d3.extent(renderData.map(e => e[1] - e[0])))
.range([0, elementHeight]);
const svg = d3
.select("#app")
.append("svg")
.attr("class", "d3")
.attr("width", elementWidth)
.attr("height", elementHeight);
const bar = svg.selectAll(".bar").data(renderData);
const barEnter = bar
.enter()
.append("rect")
.attr("class", "bar")
.attr("fill", (d, i) => {
return paint(i);
})
.attr("x", (d, i) => i * 2 + i)
.attr("y", d => elementHeight - height(d[1] - d[0]))
.attr("width", 2)
.attr("height", d => height(d[1] - d[0]));
function reRender() {
currentTime += 1
barEnter.attr("fill", (d, i) => {
return paint(i);
})
}
window.setInterval(function () {
reRender()
}, 1000);
function paint(i) {
barPos = i / renderData.length * duration;
if (barPos <= currentTime) {
return "red";
} else if (barPos <= buffered) {
return "orange";
} else {
return "grey";
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id='app' />
</body>
</html>
Codepen

Change React State with SVG Rect On Click handler

Generally I make sure to include a code example for my problem, however in this case my code is 100% similar to the following D3 Radio Button example, which I am simply trying to include in a react component of mine.
The relevant code from the example is the on-click handler:
.on("click",function(d,i) {
updateButtonColors(d3.select(this), d3.select(this.parentNode))
d3.select("#numberToggle").text(i+1)
});
however, rather than toggling a number, I am trying to change the state of my react app when this radio button is clicked. For now, let's say I'm simply trying to set the state to be one of 1, 2, or 3, that way (i + 1) is the state I'd like to set.
I tried calling setState() directly in the on click handler here, however my state didn't change. Any thoughts on how I can do this? Let me know if more of my code is needed here.
Edit: I've tried adding a snippet of what I have so far, but i'm struggling to get it to work here on stackoverflow.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
chartType: 1
}
}
drawChartTypeButton() {
// colors for different button states
const defaultColor= "#7777BB"
const hoverColor= "#0000ff"
const pressedColor= "#000077"
const bWidth= 8; //button width
const bHeight= 5; //button height
const bSpace= 1; //space between buttons
const x0 = 5; //x offset
const y0 = 5; //y offset
const labels = [1, 2, 3];
const updateButtonColors = function(button, parent) {
parent.selectAll("rect")
.attr("fill",defaultColor)
button.select("rect")
.attr("fill",pressedColor)
}
// groups for each button (which will hold a rect and text)
const chartTypeButton = d3.select('g.allbuttons')
const buttonGroups= chartTypeButton.selectAll("g.button")
.data(labels)
.enter()
.append("g")
.attr("class", "button")
.style("cursor", "pointer")
.on("click", function(d,i) {
updateButtonColors(d3.select(this), d3.select(this.parentNode))
this.setState({chartType: 2})
})
.on("mouseover", function() {
if (d3.select(this).select("rect").attr("fill") != pressedColor) {
d3.select(this)
.select("rect")
.attr("fill",hoverColor);
}
})
.on("mouseout", function() {
if (d3.select(this).select("rect").attr("fill") != pressedColor) {
d3.select(this)
.select("rect")
.attr("fill",defaultColor);
}
})
buttonGroups.append("rect")
.attr("class","buttonRect")
.attr("width",bWidth)
.attr("height",bHeight)
.attr("x", function(d,i) {return x0+(bWidth+bSpace)*i;})
.attr("y",y0)
.attr("rx",1) //rx and ry give the buttons rounded corners
.attr("ry",1)
.attr("fill",defaultColor)
// adding text to each toggle button group, centered
// within the toggle button rect
buttonGroups.append("text")
.attr("class","buttonText")
.attr("font-family", "arial")
.attr("font-size", "0.1em")
.attr("x",function(d,i) {
return x0 + (bWidth+bSpace)*i + bWidth/2;
})
.attr("y",y0)
.attr("text-anchor","middle")
.attr("dominant-baseline","central")
.attr("fill","black")
.text(function(d) {return d;})
}
componentDidMount() {
const chart = d3.select('.chart')
.attr('width', 320)
.attr('height', 240)
.attr("viewBox", "0, 0, " + 50 + ", " + 50 + "")
this.drawChartTypeButton();
}
render() {
return(
<div className='container'>
<svg className='chart'>
<g className="allbuttons" />
</svg>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id='root'>
Damnit Work
</div>
You seem to be mixing up the this scope inside the click handler, you both use the this for the d3 selector as for the react component.
Normally we could retain the this scope using arrow functions, but as you seem to need it for d3 aswell, just create a local variable that saves the current context, so you can reuse it in your click function
// create a local reference to "this" in the drawCharTypeButton function
const self = this;
// use the local reference to update the componenents state
.on("click", function(d,i) {
updateButtonColors(d3.select(this), d3.select(this.parentNode));
self.setState({chartType: 2});
})
Then your current code would be working (true it only shows the 3 buttons, and selects either of the 3)
Please note that in your sample code, the chartWidth and chartHeight variable were undefined, so I set them to 320x240 so it matches a bit with the rendering space here on SO
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
chartType: 1
}
}
drawChartTypeButton() {
// colors for different button states
const defaultColor= "#7777BB"
const hoverColor= "#0000ff"
const pressedColor= "#000077"
const bWidth= 8; //button width
const bHeight= 6; //button height
const bSpace= 0.5; //space between buttons
const x0 = 5; //x offset
const y0 = 14; //y offset
const labels = [1, 2, 3];
const updateButtonColors = function(button, parent) {
parent.selectAll("rect")
.attr("fill",defaultColor)
button.select("rect")
.attr("fill",pressedColor)
}
// groups for each button (which will hold a rect and text)
const self = this;
const chartTypeButton = d3.select('g.allbuttons')
const buttonGroups= chartTypeButton.selectAll("g.button")
.data(labels)
.enter()
.append("g")
.attr("class", "button")
.style("cursor", "pointer")
.on("click", function(d,i) {
updateButtonColors(d3.select(this), d3.select(this.parentNode))
self.setState({chartType: 2})
})
.on("mouseover", function() {
if (d3.select(this).select("rect").attr("fill") != pressedColor) {
d3.select(this)
.select("rect")
.attr("fill",hoverColor);
}
})
.on("mouseout", function() {
if (d3.select(this).select("rect").attr("fill") != pressedColor) {
d3.select(this)
.select("rect")
.attr("fill",defaultColor);
}
})
buttonGroups.append("rect")
.attr("class","buttonRect")
.attr("width",bWidth)
.attr("height",bHeight)
.attr("x", function(d,i) {return x0+(bWidth+bSpace)*i;})
.attr("y",y0)
.attr("rx",5) //rx and ry give the buttons rounded corners
.attr("ry",5)
.attr("fill",defaultColor)
// adding text to each toggle button group, centered
// within the toggle button rect
buttonGroups.append("text")
.attr("class","buttonText")
.attr("font-family", "arial")
.attr("font-size", "0.1em")
.attr("x",function(d,i) {
return x0 + (bWidth+bSpace)*i + bWidth/2;
})
.attr("y",y0+bHeight/2)
.attr("text-anchor","middle")
.attr("dominant-baseline","central")
.attr("fill","white")
.text(function(d) {return d;})
}
componentDidMount() {
const chart = d3.select('.chart')
.attr('width', 160)
.attr('height', 120)
.attr("viewBox", "0, 0, " + 50 + ", " + 50 + "")
this.drawChartTypeButton();
}
render() {
return(
<div className='container'>
<svg className='chart'>
<g className="allbuttons" />
</svg>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id='root'>
Damnit Work
</div>
A nitpick on the combination of d3, react, best practice, you should try to do all DOM manipulations inside react instead.
Now for a chart that might not be completely possible, but those 3 buttons can easily be rendered without the need of d3
I haven't combined these rendering engines yet, so I cannot really say if there are downsides to your current approach

How can I display tooltips from multiple maps when mousover on one of the maps in D3js

I have a dashboard with two separate maps of a state showing different data based on years 2014 and 2012. The map when hovered over show the name of area individually. What I need to do is display both 2012 and 2014 maps's tooltips at the same time over the respective maps when I mouseover any one of the two maps. How can I display both at the same time. I would appreciate any help with this. Thanks.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test dashboard</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" >
<style>
#gujarat-viz-2017, #buttons {
border-right: 1px solid #ccc
}
.container {
background-color: #d5e8ec;
}
.const0 {
display: none;
}
.emptyparty {
fill:#f9f9f1;
}
.emptyparty:hover, .constituency:hover {
fill:#ccc;
}
.hidden { display: none; }
.showtooltip { position: absolute; z-index: 10000; background-color: #333;
border-radius: 10px; color: #fff; padding: 5px; }
/*Party colors*/
.bjp{ fill: #f88101;}
.inc{ fill: #6da736;}
.ncp{ fill: #076598;}
.gpp{ fill: #5a469d;}
.ind{ fill: #25a29a;}
.jdu{ fill: #eb4d4c;}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div id="gujarat-viz-2014" class="col-md-6">
<h2>2014</h2>
</div>
<div id="gujarat-viz-2012" class="col-md-6">
<h2>2012</h2>
</div>
</div> <!-- .row -->
</div>
<script src="http://www.thehindu.com/static/js/jquery-1.10.2.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script>
function map_function(map_settings){
// Global variables
var margin = { top: 50, left:50, right:50, bottom:50 },
height = 400 - margin.top - margin.bottom,
width = 500 - margin.left - margin.right;
// Create SVG canvas with responsive resizing
var svg = d3.select(map_settings["htmlelement"])
.append("svg")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("preserveAspectRatio", "xMinYMin")
.append("g")
.attr("class", "data"+map_settings["year"])
// Add a tooltip to visualization
var tooltip = d3.select('body').append('div')
.attr('class', 'hidden showtooltip')
.attr('id', "tooltip"+map_settings["year"])
// queue and read the topojson, json data file
d3.queue()
.defer(d3.json, "https://api.myjson.com/bins/17m3if")
.defer(d3.json, map_settings.data)
.await(render_map)
var projection = d3.geoMercator()
.scale(3000)
.center([71.5, 22.3])
.translate([width / 2, height / 2])
var geoPath = d3.geoPath()
.projection(projection)
function render_map(error, mapshape, mapdata){
var constituency = topojson.feature(mapshape, mapshape.objects.collection).features;
dataMap = {};
mapdata.forEach(function(d){
dataMap[d.constNo] = d;
})
var fill_function = function(d) {
// d3.select(this).attr('fill', "white")
} // end of mousemove_function
var mousemove_function = function(d) {
var constinfo = dataMap[d.properties.AC_NO];
// console.log(constinfo.constituencyName)
// console.log(d3.select(this).data()[0].properties)
var html = "<p>"+constinfo.constituencyName+"</p>"
tooltip.classed('hidden', false)
.html(html)
.style("left", (d3.event.clientX - 10) + "px")
.style("top", (d3.event.clientY - 45) + "px");
} // end of mousemove_function
var class_function = function(d) {
var constinfo = dataMap[d.properties.AC_NO];
var className = "constituency ";
if(constinfo !== undefined) {
className += ("c"+constinfo.constNo+" ")
className += constinfo.leadingParty.replace(/[^a-zA-Z ]/g, "").toLowerCase()
} else {
className += "emptyparty"
className += " const"
className += d.properties.AC_NO
}
return className;
} // end of class_function
var mouseout_function = function(d) {
tooltip.classed('hidden', true)
} // end of mousemove_function
svg.selectAll(".constituency")
.data(constituency)
.enter().append("path")
.attr("d", geoPath)
.attr('class', class_function)
.attr('fill', "white")
.attr('stroke', "#e8e8e8")
.attr('stroke-width', "0.5")
.on('mouseover', mousemove_function)
.on('mouseout', mouseout_function)
} // render_map
} // map_function
var gujarat_data_2014 = {
htmlelement: "#gujarat-viz-2014",
data: "https://api.myjson.com/bins/yolfr",
year: "2014"
};
var gujarat_data_2012 = {
htmlelement: "#gujarat-viz-2012",
data: "https://api.myjson.com/bins/19ztxj",
year: "2012"
};
map_function(gujarat_data_2014);
map_function(gujarat_data_2012);
</script>
</body>
</html>
I'd modify your mousemove and mouseout to operate on both maps at the same time:
var mousemove_function = function(d) {
var constinfo = dataMap[d.properties.AC_NO];
var html = "<p>" + constinfo.constituencyName + "</p>"
var tooltips = d3.selectAll('.showtooltip');
// get paths from all maps
d3.selectAll('.c' + constinfo.constNo)
.each(function(d,i){
var pos = this.getBoundingClientRect();
// operate on appropriate tooltip
d3.select(tooltips.nodes()[i]).classed('hidden', false)
.html(html)
.style("left", (pos.x + pos.width/2) + "px")
.style("top", (pos.y - pos.height/2) + "px");
});
} // end of mousemove_function
var mouseout_function = function(d) {
d3.selectAll('.showtooltip').classed('hidden', true);
} // end of mousemove_function
Running code here.

Categories

Resources