I am currently testing out d3's join, enter, update, exit and desiring to produce something like
chart update 1 or chart update 2.
To achieve this, I have built a HTML dropdown with select element from a dataset and I expect to wire up the dropdown to the viz to achieve at least updated values as per the dropdown selection. But it is failing.
It is a hard concept to grasp and I am not sure where the code is failing. If any value is selected from the dropdown, the chart does not update at all.
////////////////////////////////////////////////////////////
//////////////////////// 00 BUILD DATA//////// /////////////
////////////////////////////////////////////////////////////
//desired permutation length
const length = 4;
//build array from the above length
const perm = Array.from(Array(length).keys()).map((d) => d + 1);
//generate corresponding alphabets for name
const name = perm.map((x) => String.fromCharCode(x - 1 + 65));
//permutation function - https://stackoverflow.com/questions/9960908/permutations-in-javascript/24622772#24622772
function permute(permutation) {
var length = permutation.length,
result = [permutation.slice()],
c = new Array(length).fill(0),
i = 1,
k, p;
while (i < length) {
if (c[i] < i) {
k = i % 2 && c[i];
p = permutation[i];
permutation[i] = permutation[k];
permutation[k] = p;
++c[i];
i = 1;
result.push(permutation.slice());
} else {
c[i] = 0;
++i;
}
}
return result;
};
//generate permutations
const permut = permute(perm);
//generate year based on permutation
const year = permut.map((x, i) => i + 2000);
//generate a yearly constant based on year to generate final value as per the rank {year-name}
const constant = year.map(d => Math.round(d * Math.random()));
const src =
year.map((y, i) => {
return name.map((d, j) => {
return {
Name: d,
Year: y,
Rank: permut[i][j],
Const: constant[i],
Value: Math.round(constant[i] / permut[i][j])
};
});
}).flat();
//console.log(src);
////////////////////////////////////////////////////////////
//////////////////////// 0 BUILD HTML DROPDOWN /////////////
////////////////////////////////////////////////////////////
d3.select('body')
.append('div', 'dropdown')
.style('position', 'absolute')
.style('top', '400px')
.append('select')
.attr('name', 'input')
.classed('Year', true)
.selectAll('option')
.data(year)
.enter()
.append('option')
//.join('option')
.text((d) => d)
.attr("value", (d) => d )
//get the dropdown value
const filterYr = parseFloat(d3.select('.Year').node().value);
////////////////////////////////////////////////////////////
//////////////////////// 1 DATA WRANGLING //////////////////
////////////////////////////////////////////////////////////
const xAccessor = (d) => d.Year;
const yAccessor = (d) => d.Value;
////////////////////////////////////////////////////////////
//////////////////////// 2 CREATE SVG //////////////////////
////////////////////////////////////////////////////////////
//namespace
//define dimension
const width = 1536;
const height = 720;
const svgns = "http://www.w3.org/2000/svg";
const svg = d3.select("svg");
svg.attr("xmlns", svgns).attr("viewBox", `0 0 ${width} ${height}`);
svg
.append("rect")
.attr("class", "vBoxRect")
//.style("overflow", "visible")
.attr("width", `${width}`)
.attr("height", `${height}`)
.attr("stroke", "black")
.attr("fill", "white");
////////////////////////////////////////////////////////////
//////////////////////// 3 CREATE BOUND ////////////////////
////////////////////////////////////////////////////////////
const padding = {
top: 70,
bottom: 100,
left: 120,
right: 120
};
const multiplierH = 1; //controls the height of the visual container
const multiplierW = 1; //controls the width of the visual container
const boundHeight = height * multiplierH - padding.top - padding.bottom;
const boundWidth = width * multiplierW - padding.right - padding.left;
//create BOUND rect -- to be deleted later
svg
.append("rect")
.attr("class", "boundRect")
.attr("x", `${padding.left}`)
.attr("y", `${padding.top}`)
.attr("width", `${boundWidth}`)
.attr("height", `${boundHeight}`)
.attr("fill", "white");
//create bound element
const bound = svg
.append("g")
.attr("class", "bound")
.style("transform", `translate(${padding.left}px,${padding.top}px)`);
function draw() {
// filter data as per dropdown
const data = src.filter(a => a.Year == filterYr);
////////////////////////////////////////////////////////////
//////////////////////// 4 CREATE SCALE ////////////////////
////////////////////////////////////////////////////////////
const scaleX = d3
.scaleLinear()
.range([0, boundWidth])
.domain(d3.extent(data, xAccessor));
const scaleY = d3
.scaleLinear()
.range([boundHeight, 0])
.domain(d3.extent(data, yAccessor));
bound.append('g')
.classed('textContainer', true)
.selectAll('text')
.data(data)
.join(
enter => enter.append('text')
.attr('x', (d, i) => scaleX(d.Year))
.attr('y', (d, i) => i)
.attr('dy', (d, i) => i * 30)
.text((d) => d.Year + '-------' + d.Value.toLocaleString())
.style("fill", "blue"),
update =>
update
.transition()
.duration(500)
.attr('x', (d, i) => scaleX(d.Year))
.attr('y', (d, i) => i)
.attr('dy', (d, i) => i * 30)
.text((d) => d.Year + '-------' + d.Value.toLocaleString())
.style("fill", "red")
/*,
(exit) =>
exit
.style("fill", "black")
.transition()
.duration(1000)
.attr("transform", (d, i) => `translate(${300},${30 + i * 30})`)
.remove()*/
)
}
draw();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
<svg>
</svg>
<!--d3 script-->
<script type="text/javascript">
</script>
</body>
</html>
You have a dropdown, but you're not listening to it. For example:
select.on("change", () => {
const filterYr = parseFloat(d3.select('.Year').node().value);
draw(filterYr);
});
Which alternatively can also be:
select.on("change", event => {
const filterYr = +event.currentTarget.value;
draw(filterYr);
});
Note that I'm passing filterYr to the draw() function as an argument. Also, do not append the containing <g> inside draw(), otherwise you'll have only the enter selection.
Here's your code with those changes:
////////////////////////////////////////////////////////////
//////////////////////// 00 BUILD DATA//////// /////////////
////////////////////////////////////////////////////////////
//desired permutation length
const length = 4;
//build array from the above length
const perm = Array.from(Array(length).keys()).map((d) => d + 1);
//generate corresponding alphabets for name
const name = perm.map((x) => String.fromCharCode(x - 1 + 65));
//permutation function - https://stackoverflow.com/questions/9960908/permutations-in-javascript/24622772#24622772
function permute(permutation) {
var length = permutation.length,
result = [permutation.slice()],
c = new Array(length).fill(0),
i = 1,
k, p;
while (i < length) {
if (c[i] < i) {
k = i % 2 && c[i];
p = permutation[i];
permutation[i] = permutation[k];
permutation[k] = p;
++c[i];
i = 1;
result.push(permutation.slice());
} else {
c[i] = 0;
++i;
}
}
return result;
};
//generate permutations
const permut = permute(perm);
//generate year based on permutation
const year = permut.map((x, i) => i + 2000);
//generate a yearly constant based on year to generate final value as per the rank {year-name}
const constant = year.map(d => Math.round(d * Math.random()));
const src =
year.map((y, i) => {
return name.map((d, j) => {
return {
Name: d,
Year: y,
Rank: permut[i][j],
Const: constant[i],
Value: Math.round(constant[i] / permut[i][j])
};
});
}).flat();
//console.log(src);
////////////////////////////////////////////////////////////
//////////////////////// 0 BUILD HTML DROPDOWN /////////////
////////////////////////////////////////////////////////////
const select = d3.select('body')
.append('div', 'dropdown')
.style('position', 'absolute')
.style('top', '400px')
.append('select')
.attr('name', 'input')
.classed('Year', true);
select.selectAll('option')
.data(year)
.enter()
.append('option')
//.join('option')
.text((d) => d)
.attr("value", (d) => d)
//get the dropdown value
const filterYr = parseFloat(d3.select('.Year').node().value);
select.on("change", event => {
const filterYr = +event.currentTarget.value;
draw(filterYr);
});
////////////////////////////////////////////////////////////
//////////////////////// 1 DATA WRANGLING //////////////////
////////////////////////////////////////////////////////////
const xAccessor = (d) => d.Year;
const yAccessor = (d) => d.Value;
////////////////////////////////////////////////////////////
//////////////////////// 2 CREATE SVG //////////////////////
////////////////////////////////////////////////////////////
//namespace
//define dimension
const width = 1536;
const height = 720;
const svgns = "http://www.w3.org/2000/svg";
const svg = d3.select("svg");
svg.attr("xmlns", svgns).attr("viewBox", `0 0 ${width} ${height}`);
svg
.append("rect")
.attr("class", "vBoxRect")
//.style("overflow", "visible")
.attr("width", `${width}`)
.attr("height", `${height}`)
.attr("stroke", "black")
.attr("fill", "white");
////////////////////////////////////////////////////////////
//////////////////////// 3 CREATE BOUND ////////////////////
////////////////////////////////////////////////////////////
const padding = {
top: 70,
bottom: 100,
left: 120,
right: 120
};
const multiplierH = 1; //controls the height of the visual container
const multiplierW = 1; //controls the width of the visual container
const boundHeight = height * multiplierH - padding.top - padding.bottom;
const boundWidth = width * multiplierW - padding.right - padding.left;
//create BOUND rect -- to be deleted later
svg
.append("rect")
.attr("class", "boundRect")
.attr("x", `${padding.left}`)
.attr("y", `${padding.top}`)
.attr("width", `${boundWidth}`)
.attr("height", `${boundHeight}`)
.attr("fill", "white");
//create bound element
const bound = svg
.append("g")
.attr("class", "bound")
.style("transform", `translate(${padding.left}px,${padding.top}px)`);
const g = bound.append('g')
.classed('textContainer', true);
function draw(filterYr) {
// filter data as per dropdown
const data = src.filter(a => a.Year == filterYr);
////////////////////////////////////////////////////////////
//////////////////////// 4 CREATE SCALE ////////////////////
////////////////////////////////////////////////////////////
const scaleX = d3
.scaleLinear()
.range([0, boundWidth])
.domain(d3.extent(data, xAccessor));
const scaleY = d3
.scaleLinear()
.range([boundHeight, 0])
.domain(d3.extent(data, yAccessor));
g.selectAll('text')
.data(data)
.join(
enter => enter.append('text')
.attr('x', (d, i) => scaleX(d.Year))
.attr('y', (d, i) => i)
.attr('dy', (d, i) => i * 30)
.text((d) => d.Year + '-------' + d.Value.toLocaleString())
.style("fill", "blue"),
update =>
update
.transition()
.duration(500)
.attr('x', (d, i) => scaleX(d.Year))
.attr('y', (d, i) => i)
.attr('dy', (d, i) => i * 30)
.text((d) => d.Year + '-------' + d.Value.toLocaleString())
.style("fill", "red")
/*,
(exit) =>
exit
.style("fill", "black")
.transition()
.duration(1000)
.attr("transform", (d, i) => `translate(${300},${30 + i * 30})`)
.remove()*/
)
}
draw(filterYr);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
<svg>
</svg>
<!--d3 script-->
<script type="text/javascript">
</script>
</body>
</html>
Related
demo() function is a pie chart i have a toggle button with check box toggle button whenever i toggle it demo() function is calling which is fine but it is creating copy of the pie chart whenever i am clicking on check box(toggle button) it changes the value around the chart which is working fine but it is calling the pie chart function again and again and multiple pie chart are creating with toggle button. what should i do get the new chart only once i mean it will not create a copy of pie chart and remove the old one?
async function renderTradesInfo() {
let tradeOrders = await TradesInformation();
totalTrades = tradeOrders.data.total_orders;
let ThirtydOrders = tradeOrders.data.orders_placed_30d;
let ThirtydOrder = `${ThirtydOrders}`;
let thirtydOrderTrade= $("#tradeCharts").find("#thirtydOrderTrade");
thirtydOrderTrade.html(ThirtydOrder);
let checkedValue = $('#checkbox1').is(':checked');
if(!checkedValue){
closedOrdersTrade(tradeOrders);
totalTrades = tradeOrders.data.total_orders;
demo(totalTrades);
}
$('#checkbox1').on('change', function () {
if ($(this).is(':checked')){
let openProfitOrders = tradeOrders.data.open_profit_orders;
let openProfitOrder = `${openProfitOrders} Profit`;
let tradeChartProfit= $("#tradeCharts").find(".tradeChartProfit");
tradeChartProfit.html(openProfitOrder);
let openLossOrders = tradeOrders.data.open_loss_orders;
let openLossOrder = `${openLossOrders} Loss`;
let tradeChartLoss= $("#tradeCharts").find(".tradeChartLoss");
tradeChartLoss.html(openLossOrder);
// totalTrades = tradeOrders.data.open_orders;
demo(totalTrades);
}
else {
closedOrdersTrade(tradeOrders);
// totalTrades = tradeOrders.data.closed_orders;
demo(totalTrades);
}
});
}
This is the piechart function which i want to update on toggle but it is creating new piechart
function demo(totalTrades){
const chartData = [
{ name: 'Loss', value: 10},
{ name: 'Profit', value: 90}
]
const colors = ['#EB5757', '#1EE8B7']
const margin = { top: 10, right: 0, bottom: 0, left: 45 },
width = 190 - margin.right - margin.left,
height = 210,
radius = width / 2
const colorScale = d3.scaleOrdinal().range(colors)
const arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(radius - 25)
const textArc = d3.arc()
.outerRadius(radius - 50)
.innerRadius(radius - 50)
const pie = d3.pie()
.sort(null)
.value(d => d.value)
const svg = d3.select('.wrapper')
.append('svg')
.attr('transform', `translate(${margin.left}, 0)`)
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', `translate(${width / 2}, ${height / 2 - 30})`)
const g = svg.selectAll('.arc')
.data(pie(chartData))
.enter().append('g')
.attr('class', 'arc')
g.append('path')
.attr('d', arc)
.style('fill', d => colorScale(d.data.name))
.style('stroke', 'none')
.transition()
.ease(d3.easeLinear)
.duration(1000)
.attrTween('d', chartTween)
// g.append('text')
// .transition()
// .ease(d3.easeLinear)
// .duration(1000)
// .attr('transform', d => `translate(${textArc.centroid(d)})`)
// .attr('dx', '-3em')
// .attr('dy', '.75em')
// .text(d => d.data.value)
// .attr('font-size', '20px')
// .attr('fill', '#fff')
g.append('text')
.html('Total Trades')
.attr('text-anchor', 'middle')
.attr('class', 'center-text')
.attr('transform', d => 'translate(0, -10)')
.attr('fill', '#fff')
g.append('text')
.html(totalTrades)
.attr('text-anchor', 'middle')
.attr('class', 'center-text')
.attr('transform', d => 'translate(0, 10)')
.attr('fill', '#FFFFFF')
svg.append('g')
.attr('class', 'legendLinear')
.attr('transform', 'translate(70, 330)')
const colorLegend = d3.legendColor()
.orient('horizontal')
.shapeWidth(60)
.scale(colorScale)
svg.select('.legendLinear')
.call(colorLegend)
// helper functions
function chartTween(b) {
b.innerRadius = 0
const i = d3.interpolate({ startAngle: 0, endAngle: 0 }, b)
return function (t) { return arc(i(t)) }
}
}
I'm currently working on trying to create a "facet plot" within d3.js, which I realize in D3 language isn't really a thing. However, I found this thread which outlined how to accomplish the task. From this post, I've converted the line chart from vertical to horizontal, and added a few extra padding elements where needed.
Now I would like to add a tooltip to each plot. From working with other pieces of code, this seems quite challenging. For some reason I can't figure out how to attach the tooltip to each individual plot. Any thoughts on how I might be able to accomplish this?
What it currently looks like:
// Data and manipluation:
const data = d3.range(25).map(i => ({
bib: Math.floor(i / 5) + 1,
ratio: -1 + Math.random() * 5,
run: [1, 2, 3, 4, 5][i % 5],
run: [1, 2, 3, 4, 5][i % 5],
name: ['metric1', 'metric2', 'metric3', 'metric4', 'metric5'][Math.floor(i / 5)]
}));
const grouped = d3.group(data,d=>d.bib);
// Dimensions:
const height = 800;
const width = 700;
const margin = {
top: 10,
left: 50,
right: 50,
bottom: 50
}
const padding = 30;
const doublePadding = padding * 2;
const plotHeight = (height-doublePadding)/grouped.size - padding;
const plotWidth = width-padding*2;
// const plotWidth = (width-padding)/grouped.size - padding;
// const plotHeight = height-padding*2;
const svg = d3.select("#chart1")
.append("svg")
.attr("width", margin.left+width+margin.right)
.attr("height", margin.top+height+margin.bottom+(padding*grouped.size));
const g = svg.append("g")
.attr("transform","translate("+[margin.left,margin.top]+")");
//Scales:
const xScale = d3.scaleLinear()
.domain(d3.extent(data, d => d.run))
.range([0, plotWidth]);
const yScale = d3.scaleLinear()
.domain(d3.extent(data, d => d.ratio))
.range([plotHeight,0]);
// Place plots:
const plots = g.selectAll(null)
.data(grouped)
.enter()
.append("g")
.attr("transform", function(d,i) {
return "translate("+[padding,i*(doublePadding+plotHeight)+padding]+")";
})
//Optional plot background:
plots.append("rect")
.attr("width",plotWidth)
.attr("height",plotHeight)
.attr("fill","#ddd");
// Plot actual data
plots.selectAll(null)
.data(d=>d[1])
.enter()
.append("circle")
.attr("r", 4)
.attr("cy", d=>yScale(d.ratio))
.attr("cx", d=>xScale(d.run))
// Plot line if needed:
plots.append("path")
.attr("d", function(d) {
return d3.line()
.x(d=>xScale(d.run))
.y(d=>yScale(d.ratio))
(d[1])
})
.attr("stroke", "#333")
.attr("stroke-width", 1)
.attr("fill","none")
// Plot names if needed:
plots.append("text")
.attr("x", plotWidth/2)
.attr("y", -10)
.text(function(d) {
return d[1][0].name;
})
.attr("text-anchor","middle");
// Plot axes
plots.append("g")
.attr("transform","translate("+[0,plotHeight]+")")
.call(d3.axisBottom(xScale).ticks(4));
plots.append("g")
.attr("transform","translate("+[-padding,0]+")")
.call(d3.axisLeft(yScale))
<head>
<!-- Load d3.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.6.1/d3.min.js"></script>
</head>
<body>
<div class="graph-container">
<div id="chart1"></div>
</div>
</body>
See a solution in a fiddle:
Add a g element with background and text(s):
const tooltip = d3.select('svg')
.append('g')
.style('visibility', 'hidden');
tooltip.append('rect')
.attr('width', 100)
.attr('height', 50)
.style('fill', '#fff')
.style('stroke', '#000')
tooltip.append('text')
.attr('x', 20)
.attr('y', 20)
Update it on mouseenter, mousevove, mouseleave:
plots.append("rect")
.attr("width",plotWidth)
.attr("height",plotHeight)
.attr("fill","#ddd")
.on("mouseenter", (e, d) => {
tooltip.style('visibility', 'visible');
tooltip.select('text').text(d[0]);
})
.on("mouseleave", () => tooltip.style('visibility', 'hidden'))
.on("mousemove", e => tooltip.attr('transform', `translate(${e.clientX},${e.clientY})`))
How I can drag a chart smoothly?
The chart is construct from two charts that candlestick and line chart.
The line chart is corresponding to candle chart, The line indicates low value of each candle.
these charts are overlap on a graph area.
This graph is able to drag horizontally, but it is very choppy.
About 750 candles does exists in my chart.
Drag become smoothly when the line chart is disabled, so seems like it caused by line chart.
I suppose to this graph taking time to redraw all of path commands in line.
But I dont know what make it choppy actually.
How I can fix it?
margin = {top: 0, bottom: 20, left: 40, right: 20}
const f = async () => {
let num
let res = await fetch("https://gist.githubusercontent.com/KiYugadgeter/f2f861798257118cb420c9cdb1f830f6/raw/b2c4217e569b3b2064f88bb7ac2f8a1e328ff516/data2.csv")
let d = await res.text()
let min_value = 999999999
let max_value = 0
data = parsed_data = d3.csvParse(d, (dt) => {
const j = dt.date.split("-").map((i) => {
return parseInt(i)
})
const date = new Date(...j)
high_value = parseFloat(dt.high)
low_value = parseFloat(dt.low)
open_value = parseFloat(dt.open)
close_value = parseFloat(dt.close)
if (low_value < min_value) {
min_value = low_value
}
if (high_value > max_value) {
max_value = high_value
}
return {
open: open_value,
close: close_value,
high: high_value,
low: low_value,
date: date
}
})
return {data: data, min: min_value, max: max_value}
}
f().then((d) => {
let num = 0
let recently_date = d.data[d.data.length-1].date
let minvalue = 99999999999
let maxvalue = 0
recently_date.setMinutes(recently_date.getMinutes() - (recently_date.getMinutes() % 30))
recently_date.setSeconds(0)
recently_date.setMilliseconds(0)
const limit_date = (recently_date - (60 * 90 * 1000))
const data = d.data.filter((d) => {
num++
if (d.low < minvalue) {
minvalue = d.low
}
if (d.high > maxvalue) {
maxvalue = d.high
}
return true
})
const svg = d3.select("svg")
const xScale = d3.scaleTime().domain(
[
limit_date,
recently_date
]
).nice().range([margin.left, 600-margin.left-margin.right])
const yScale = d3.scaleLinear().domain([(maxvalue - (maxvalue%1000) + 1000), (minvalue - (minvalue%1000) - 1000)]).range([0, 410-margin.top-margin.bottom])
//console.log(recently_date, limit_date)
const canvas = svg.append("g").attr("width", 600).attr("height", 410)
const xaxis = d3.axisBottom(xScale).ticks().tickFormat(d3.timeFormat("%H:%M"))
//console.log(canvas.attr("width"))
//console.log(svg.node().width.baseVal.value-300)
const clip = svg.append("clipPath").attr("id", "clip").append("rect").attr("width", 600-margin.left-margin.right).attr("height", 410-margin.bottom-margin.top).attr("x", margin.left).attr("y", 0)
const g = canvas // Data group
.append("g")
.attr("stroke-linecap", "square")
.attr("stroke", "black")
.attr("clip-path", "url(#clip)")
.selectAll("g")
.data(data)
.join("g")
.classed("ticks", true)
/*.attr("transform", (d) => {
return `translate(${xScale(d.date)},0)`
}
)*/
g.append("line")
.attr("y1", (d) => yScale(d.high))
.attr("y2", (d) => yScale(d.low))
.attr("x1", (d) => xScale(d.date))
.attr("x2", (d) => xScale(d.date))
.attr("stroke-width", 0.6)
.classed("line_high_low", true)
g.append("line")
.attr("y1", (d) => yScale(d.open))
.attr("y2", (d) => yScale(d.close))
.attr("x1", (d) => xScale(d.date))
.attr("x2", (d) => xScale(d.date))
.attr("stroke-width", 3)
.attr("stroke", (d) => d.open > d.close ? d3.schemeSet1[0]
: d.close > d.open ? d3.schemeSet1[2] : d3.schemeSet1[8]
)
.classed("line_open_close", true)
const linefunc = (xScale, yScale) => {
return d3.line()
.x((d) => {
if (isNaN(d.date)) {
return 0
}
let retval = xScale(d.date)
return retval
})
.y((d) => {
if (isNaN(d.low)) {
return 0
}
let retval = yScale(d.low)
return retval
})
}
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "#00ff00")
.attr("d", linefunc(xScale, yScale))
const group_x = canvas
.append("g")
.attr("transform", "translate(0," + String(410-margin.top - margin.bottom) + ")") // X axis
.call(xaxis).style("font-size", "5")
const yaxis = d3.axisLeft(yScale)
const group_y = canvas
.append("g")
.attr("class", "axis axis--y")
.attr("transform", "translate(" + String(margin.left) + ",0)") // Y axis
.call(yaxis)
.style("font-size", "5")
zoom_func = d3.zoom().on("zoom", (e) => {
let newX = e.transform.rescaleX(xScale)
let newscale = xaxis.scale(newX)
group_x.call(newscale)
//g.selectAll(".ticks").data(data).join(".ticks line").attr("x1", (d) => {
g.selectAll("line").join("line").attr("x1", (d) => {
return newX(d.date)
}).attr("x2", (d) => {return newX(d.date)})
g.selectAll("path").attr("transform", `translate(${e.transform.x} 0)`)
//.attr("transform", (d) => `translate({$newX(d.date)}, 0)`) this is not use
})
svg.call(zoom_func)
})
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<script src="node_modules/d3/dist/d3.min.js"></script>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="index.css">
</head>
<body>
<svg id="svg" viewBox="0 0 600 410">
</svg>
<script src="https://d3js.org/d3.v6.min.js"></script>
</body>
</html>
I'm trying to append a line to an end of an area chart path. The most difficult part my area is animated. I have a clipPath which width being transformed from width: 0 to width: 960 and the line at the end goes along with it so shoud be synchronised. Also the text on top of that line needs to be updated while it goes along.
Desired output:
My initial idea was to build a chart area and add a clipPath and then add a bar chart inside of area chart so I can update my text based on the bar appended, however bars are not inside my area chart. What am I doing wrong to place bars inside area chart or is there a better solution to this?
// Area chart width and height
const width1 = 1000,
height1 = 100;
// Define x and y scale for area chart
const xScale1 = d3.scaleTime().range([0, width1]);
const yScale1 = d3.scaleLinear().range([height1, 0]);
// Define x and y range for bar chart
let xScale2 = d3.scaleBand().range([0, width1]);
let yScale2 = d3.scaleLinear().range([height1, 0]);
// Add SVG to #areachart
const svg1 = d3
.select('#areachart')
.append('svg')
.attr('viewBox', `0 0 ${width1} ${height1}`)
.attr('transform', 'translate(' + 0 + ',' + -50 + ')');
const g1 = svg1.append('g');
// Fetch data
d3.json(
'https://api.coronavirus.data.gov.uk/v1/data?filters=areaName=United%2520Kingdom;areaType=overview&structure=%7B%22areaType%22:%22areaType%22,%22areaName%22:%22areaName%22,%22areaCode%22:%22areaCode%22,%22date%22:%22date%22,%22newCasesByPublishDate%22:%22newCasesByPublishDate%22,%22cumCasesByPublishDate%22:%22cumCasesByPublishDate%22%7D&format=json'
)
.then(function(data) {
console.log('DATES SLICED ----->', data.data.slice(30, 281));
//Define xScale1 & yScale1 domain after data loaded
yScale1.domain([
0,
d3.max(data.data, function(d) {
return +d.cumCasesByPublishDate;
}),
]);
xScale1.domain(
d3.extent(data.data, function(d) {
return new Date(d.date);
})
);
// Area generator
const area = d3
.area()
.curve(d3.curveStepAfter)
.x((d) => xScale1(new Date(d.date)))
.y1((d) => yScale1(+d.cumCasesByPublishDate))
.y0(yScale1(0));
g1.append('path')
.datum(data.data.slice(30, 200))
.attr('d', area)
.classed('placeholder-layer', true)
.style('fill', '#dadada')
.style('opacity', '0.3');
// clipPath for areachart fill animation
const clip = g1.append('clipPath').attr('id', 'clip');
const clipRect = clip.append('rect').attr('width', 0).attr('height', 750);
g1.append('path')
.datum(data.data.slice(30, 200))
.attr('d', area)
.attr('clip-path', 'url(#clip)')
.classed('overlay-layer', true)
.style('fill', 'yellow')
.style('opacity', '0.3');
g1.append('line').attr('stroke-width', 960).style('stroke', 'yellow');
clipRect
.transition()
.duration(10000)
.ease(d3.easeLinear)
.attr('width', 960);
//x and y domain for bar chart
xScale2.domain(data.data.slice(30, 200).map((d) => new Date(d.date)));
yScale2.domain([
0,
d3.max(data.data, function(d) {
return +d.cumCasesByPublishDate;
}),
]);
g1.selectAll('rect')
.data(data.data.slice(30, 200))
.enter()
.append('rect')
.style('fill', 'red')
.attr('width', xScale2.bandwidth() * 10)
.attr('height', (d) => yScale2(+d.cumCasesByPublishDate))
.attr('x', 0)
.attr('y', function(d) {
return yScale2(+d.cumCasesByPublishDate);
})
.transition()
.delay(function(d, i) {
return i * 30;
})
.attr('x', function(d) {
return xScale2(new Date(d.date));
})
.duration(100);
})
// if there's an error, log it
.catch((error) => console.log(error));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<section id="map">
<div id="visualisation-container">
<div id="visualisation"></div>
<div id="areachart"></div>
</div>
</section>
I'd just use a line, no bars, and use transition.tween with d3.interpolateDate to make the text change.
// Area chart width and height
const width1 = 800,
height1 = 250,
marginBottom = 50
// Define x and y scale for area chart
const xScale1 = d3.scaleTime().range([0, width1]);
const yScale1 = d3.scaleLinear().range([height1, marginBottom]);
// Define x and y range for bar chart
let xScale2 = d3.scaleBand().range([0, width1]);
let yScale2 = d3.scaleLinear().range([height1, marginBottom]);
// Add SVG to #areachart
const svg1 = d3
.select('#areachart')
.append('svg')
.attr('width', width1)
.attr('height', height1);
const g1 = svg1.append('g');
// Fetch data
d3.json(
'https://api.coronavirus.data.gov.uk/v1/data?filters=areaName=United%2520Kingdom;areaType=overview&structure=%7B%22areaType%22:%22areaType%22,%22areaName%22:%22areaName%22,%22areaCode%22:%22areaCode%22,%22date%22:%22date%22,%22newCasesByPublishDate%22:%22newCasesByPublishDate%22,%22cumCasesByPublishDate%22:%22cumCasesByPublishDate%22%7D&format=json'
)
.then(data => {
data.data.forEach(d => {
d.cumCasesByPublishDate = +d.cumCasesByPublishDate;
d.date = new Date(d.date);
});
return data.data.slice(30, 200);
})
.then(function(data) {
//Define xScale1 & yScale1 domain after data loaded
yScale1.domain([
0,
d3.max(data, d => d.cumCasesByPublishDate),
]);
xScale1.domain(
d3.extent(data, d => d.date)
);
// Area generator
const area = d3
.area()
.curve(d3.curveStepAfter)
.x((d) => xScale1(d.date))
.y1((d) => yScale1(d.cumCasesByPublishDate))
.y0(yScale1(0));
g1.append('path')
.datum(data)
.attr('d', area)
.classed('placeholder-layer', true)
.style('fill', '#dadada')
.style('opacity', '0.3');
// clipPath for areachart fill animation
const clip = g1.append('clipPath').attr('id', 'clip');
const clipRect = clip.append('rect').attr('width', 0).attr('height', 750);
g1.append('path')
.datum(data)
.attr('d', area)
.attr('clip-path', 'url(#clip)')
.classed('overlay-layer', true)
.style('fill', 'yellow')
.style('opacity', '0.3');
const format = d3.timeFormat("%B %d, %Y");
const duration = 10000;
g1.append('line')
.attr('stroke-width', 5)
.style('stroke', 'black')
.attr('x1', xScale1.range()[0])
.attr('x2', xScale1.range()[0])
.attr('y1', yScale1.range()[0])
.attr('y2', yScale1.range()[1])
.transition()
.duration(duration)
.ease(d3.easeLinear)
.attr('x1', xScale1.range()[1])
.attr('x2', xScale1.range()[1])
g1.append('text')
.attr('x', xScale1.range()[0])
.attr('y', marginBottom / 2)
.attr('text-anchor', 'middle')
.transition()
.duration(duration)
.ease(d3.easeLinear)
.attr('x', xScale1.range()[1])
.tween('text', function() {
const i = d3.interpolateDate(xScale1.domain()[0], xScale1.domain()[1]);
return (t) => d3.select(this).text(format(i(t)));
})
})
// if there's an error, log it
.catch((error) => console.log(error));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<section id="map">
<div id="visualisation-container">
<div id="visualisation"></div>
<div id="areachart"></div>
</div>
</section>
I'm a newbie both in JSON and js. I'm trying to load json data based on this radarchart. If I add the data just after var data = MY DATA, it works but if I load json file, it doesn't. For loading data, I follow the last suggestion entitled “What does d3.json return?” (v5) of this post--in fact the others didn't worked too--; thus, I have added <script src="https://d3js.org/d3.v5.min.js"></script> in addition to <script src="https://d3js.org/d3.v4.min.js"></script> which I used for other barplots and piecharts in the same webpage.
It works
<head>
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-path.v1.min.js" charset="utf-8"></script>
<!-- source: https://gist.github.com/mthh/7e17b680b35b83b49f1c22a3613bd89f -->
<script src="../other/radarChart.js" charset="utf-8"></script>
</head>
<body>
<!-- a few barplots and piecharts -->
<!-- afterwards the following radarChart -->
<table>
<tr>
<td style="radarChart">
<div class="radarChart" style="display: inline-flex;"></div>
<script>
//////////////////////////////////////////////////////////////
//////////////////////// Set-Up //////////////////////////////
//////////////////////////////////////////////////////////////
var marginRadar = { top: 150, right: 80, bottom: 50, left: 80 },
widthRadar = Math.min(700, window.innerWidth / 4) - marginRadar.left - marginRadar.right,
heightRadar = Math.min(width, window.innerHeight - marginRadar.top - marginRadar.bottom);
//////////////////////////////////////////////////////////////
////////////////////////// Data //////////////////////////////
//////////////////////////////////////////////////////////////
var data = [
{
"name": "Clan A",
"axes": [
{"axis": "cognition", "value": 3.12},
{"axis": "communication","value": 9.38},
{"axis": "competition","value": 18.75},
{"axis": "consumption","value": 6.25},
{"axis": "contact","value": 21.88},
{"axis": "emotion","value": 6.25},
{"axis": "motion","value": 18.75},
{"axis": "perception","value": 3.12},
{"axis": "possession","value": 3.12},
{"axis": "social","value": 3.12}
]
},
{"name": "clan B",
"axes": [
{"axis": "cognition","value": 3.12},
{"axis": "communication","value": 0.00},
{"axis": "competition","value": 0.00},
{"axis": "consumption","value": 0.00},
{"axis": "contact","value": 9.38},
{"axis": "emotion","value": 0.00},
{"axis": "motion","value": 0.00},
{"axis": "perception","value": 0.00},
{"axis": "possession","value": 0.00},
{"axis": "social","value": 0.00}
]
}
];
var radarChartOptions = {
w: 290,
h: 350,
margin: marginRadar,
levels: 6,
roundStrokes: false,
color: d3.scaleOrdinal().range(["#AFC52F", "#ff6600"]),
format: '.0f',
legend: { title: 'Legend', translateX: 150, translateY: 100 },
unit: '%'
};
// Draw the chart, get a reference the created svg element :
let svg_radar = RadarChart(".radarChart", data, radarChartOptions);
</script>
</td>
</tr>
</table>
</body>
It doesn't work: loading json file [note: everything is the same before and after the following suggestion, except that I have added <script src="https://d3js.org/d3.v5.min.js"></script> between <td> and <div class="radarChart"..> so that there is no conflict with barplots and piecharts which need v4 instead of v5]:
<table>
<tr>
<td style="radarChart">
<!-- add v5 here -->
<script src="https://d3js.org/d3.v5.min.js"></script>
<div class="radarChart" style="display: inline-flex;"></div>
<!-- [same data ] -->
[...]
var data = d3.json("../other/radarChart-data.json");
console.log(data)
<!-- [same data after] -->
[...]
Thus my question is: how to load JSON file for this radarchart? I prefer to load a file since "value: XX" are not fixed, but changed according to new values adding in related xml files.
In advance, thanks so much for your kind help.
The solution suggested here works:
https://www.quora.com/How-can-I-load-data-from-a-JSON-file-into-a-variable-in-JavaScript-without-using-Ajax?fbclid=IwAR1ilYFGKBaNsUupYOwIBSVMHBDP24o7j87WP5GzZSrlwIsyLK1riU5JCRQ
It bypasses the constraints of Ajax in a beautifully simple way!
move the declaration of your var data into a separate file
load this file as a javascript
use the variable as you need to!
Important: Both imports of d3 edit the global scope, so one will override the other. I recommend to just choose v4 for the radar chart.
My answer is very long, because I had to include the RadarChart code to make it work. Just scroll down to the very end to see what I did. d3.json needs a function as a second argument, and will call that function with the result:
/////////////////////////////////////////////////////////
/////////////// The Radar Chart Function ////////////////
/// mthh - 2017 /////////////////////////////////////////
// Inspired by the code of alangrafu and Nadieh Bremer //
// (VisualCinnamon.com) and modified for d3 v4 //////////
/////////////////////////////////////////////////////////
const max = Math.max;
const sin = Math.sin;
const cos = Math.cos;
const HALF_PI = Math.PI / 2;
const RadarChart = function RadarChart(parent_selector, data, options) {
//Wraps SVG text - Taken from http://bl.ocks.org/mbostock/7555321
const wrap = (text, width) => {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.4, // ems
y = text.attr("y"),
x = text.attr("x"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
} //wrap
const cfg = {
w: 600, //Width of the circle
h: 600, //Height of the circle
margin: {
top: 20,
right: 20,
bottom: 20,
left: 20
}, //The margins of the SVG
levels: 3, //How many levels or inner circles should there be drawn
maxValue: 0, //What is the value that the biggest circle will represent
labelFactor: 1.25, //How much farther than the radius of the outer circle should the labels be placed
wrapWidth: 60, //The number of pixels after which a label needs to be given a new line
opacityArea: 0.35, //The opacity of the area of the blob
dotRadius: 4, //The size of the colored circles of each blog
opacityCircles: 0.1, //The opacity of the circles of each blob
strokeWidth: 2, //The width of the stroke around each blob
roundStrokes: false, //If true the area and stroke will follow a round path (cardinal-closed)
color: d3.scaleOrdinal(d3.schemeCategory10), //Color function,
format: '.2%',
unit: '',
legend: false
};
//Put all of the options into a variable called cfg
if ('undefined' !== typeof options) {
for (var i in options) {
if ('undefined' !== typeof options[i]) {
cfg[i] = options[i];
}
} //for i
} //if
//If the supplied maxValue is smaller than the actual one, replace by the max in the data
// var maxValue = max(cfg.maxValue, d3.max(data, function(i){return d3.max(i.map(function(o){return o.value;}))}));
let maxValue = 0;
for (let j = 0; j < data.length; j++) {
for (let i = 0; i < data[j].axes.length; i++) {
data[j].axes[i]['id'] = data[j].name;
if (data[j].axes[i]['value'] > maxValue) {
maxValue = data[j].axes[i]['value'];
}
}
}
maxValue = max(cfg.maxValue, maxValue);
const allAxis = data[0].axes.map((i, j) => i.axis), //Names of each axis
total = allAxis.length, //The number of different axes
radius = Math.min(cfg.w / 2, cfg.h / 2), //Radius of the outermost circle
Format = d3.format(cfg.format), //Formatting
angleSlice = Math.PI * 2 / total; //The width in radians of each "slice"
//Scale for the radius
const rScale = d3.scaleLinear()
.range([0, radius])
.domain([0, maxValue]);
/////////////////////////////////////////////////////////
//////////// Create the container SVG and g /////////////
/////////////////////////////////////////////////////////
const parent = d3.select(parent_selector);
//Remove whatever chart with the same id/class was present before
parent.select("svg").remove();
//Initiate the radar chart SVG
let svg = parent.append("svg")
.attr("width", cfg.w + cfg.margin.left + cfg.margin.right)
.attr("height", cfg.h + cfg.margin.top + cfg.margin.bottom)
.attr("class", "radar");
//Append a g element
let g = svg.append("g")
.attr("transform", "translate(" + (cfg.w / 2 + cfg.margin.left) + "," + (cfg.h / 2 + cfg.margin.top) + ")");
/////////////////////////////////////////////////////////
////////// Glow filter for some extra pizzazz ///////////
/////////////////////////////////////////////////////////
//Filter for the outside glow
let filter = g.append('defs').append('filter').attr('id', 'glow'),
feGaussianBlur = filter.append('feGaussianBlur').attr('stdDeviation', '2.5').attr('result', 'coloredBlur'),
feMerge = filter.append('feMerge'),
feMergeNode_1 = feMerge.append('feMergeNode').attr('in', 'coloredBlur'),
feMergeNode_2 = feMerge.append('feMergeNode').attr('in', 'SourceGraphic');
/////////////////////////////////////////////////////////
/////////////// Draw the Circular grid //////////////////
/////////////////////////////////////////////////////////
//Wrapper for the grid & axes
let axisGrid = g.append("g").attr("class", "axisWrapper");
//Draw the background circles
axisGrid.selectAll(".levels")
.data(d3.range(1, (cfg.levels + 1)).reverse())
.enter()
.append("circle")
.attr("class", "gridCircle")
.attr("r", d => radius / cfg.levels * d)
.style("fill", "#CDCDCD")
.style("stroke", "#CDCDCD")
.style("fill-opacity", cfg.opacityCircles)
.style("filter", "url(#glow)");
//Text indicating at what % each level is
axisGrid.selectAll(".axisLabel")
.data(d3.range(1, (cfg.levels + 1)).reverse())
.enter().append("text")
.attr("class", "axisLabel")
.attr("x", 4)
.attr("y", d => -d * radius / cfg.levels)
.attr("dy", "0.4em")
.style("font-size", "10px")
.attr("fill", "#737373")
.text(d => Format(maxValue * d / cfg.levels) + cfg.unit);
/////////////////////////////////////////////////////////
//////////////////// Draw the axes //////////////////////
/////////////////////////////////////////////////////////
//Create the straight lines radiating outward from the center
var axis = axisGrid.selectAll(".axis")
.data(allAxis)
.enter()
.append("g")
.attr("class", "axis");
//Append the lines
axis.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", (d, i) => rScale(maxValue * 1.1) * cos(angleSlice * i - HALF_PI))
.attr("y2", (d, i) => rScale(maxValue * 1.1) * sin(angleSlice * i - HALF_PI))
.attr("class", "line")
.style("stroke", "white")
.style("stroke-width", "2px");
//Append the labels at each axis
axis.append("text")
.attr("class", "legend")
.style("font-size", "11px")
.attr("text-anchor", "middle")
.attr("dy", "0.35em")
.attr("x", (d, i) => rScale(maxValue * cfg.labelFactor) * cos(angleSlice * i - HALF_PI))
.attr("y", (d, i) => rScale(maxValue * cfg.labelFactor) * sin(angleSlice * i - HALF_PI))
.text(d => d)
.call(wrap, cfg.wrapWidth);
/////////////////////////////////////////////////////////
///////////// Draw the radar chart blobs ////////////////
/////////////////////////////////////////////////////////
//The radial line function
const radarLine = d3.radialLine()
.curve(d3.curveLinearClosed)
.radius(d => rScale(d.value))
.angle((d, i) => i * angleSlice);
if (cfg.roundStrokes) {
radarLine.curve(d3.curveCardinalClosed)
}
//Create a wrapper for the blobs
const blobWrapper = g.selectAll(".radarWrapper")
.data(data)
.enter().append("g")
.attr("class", "radarWrapper");
//Append the backgrounds
blobWrapper
.append("path")
.attr("class", "radarArea")
.attr("d", d => radarLine(d.axes))
.style("fill", (d, i) => cfg.color(i))
.style("fill-opacity", cfg.opacityArea)
.on('mouseover', function(d, i) {
//Dim all blobs
parent.selectAll(".radarArea")
.transition().duration(200)
.style("fill-opacity", 0.1);
//Bring back the hovered over blob
d3.select(this)
.transition().duration(200)
.style("fill-opacity", 0.7);
})
.on('mouseout', () => {
//Bring back all blobs
parent.selectAll(".radarArea")
.transition().duration(200)
.style("fill-opacity", cfg.opacityArea);
});
//Create the outlines
blobWrapper.append("path")
.attr("class", "radarStroke")
.attr("d", function(d, i) {
return radarLine(d.axes);
})
.style("stroke-width", cfg.strokeWidth + "px")
.style("stroke", (d, i) => cfg.color(i))
.style("fill", "none")
.style("filter", "url(#glow)");
//Append the circles
blobWrapper.selectAll(".radarCircle")
.data(d => d.axes)
.enter()
.append("circle")
.attr("class", "radarCircle")
.attr("r", cfg.dotRadius)
.attr("cx", (d, i) => rScale(d.value) * cos(angleSlice * i - HALF_PI))
.attr("cy", (d, i) => rScale(d.value) * sin(angleSlice * i - HALF_PI))
.style("fill", (d) => cfg.color(d.id))
.style("fill-opacity", 0.8);
/////////////////////////////////////////////////////////
//////// Append invisible circles for tooltip ///////////
/////////////////////////////////////////////////////////
//Wrapper for the invisible circles on top
const blobCircleWrapper = g.selectAll(".radarCircleWrapper")
.data(data)
.enter().append("g")
.attr("class", "radarCircleWrapper");
//Append a set of invisible circles on top for the mouseover pop-up
blobCircleWrapper.selectAll(".radarInvisibleCircle")
.data(d => d.axes)
.enter().append("circle")
.attr("class", "radarInvisibleCircle")
.attr("r", cfg.dotRadius * 1.5)
.attr("cx", (d, i) => rScale(d.value) * cos(angleSlice * i - HALF_PI))
.attr("cy", (d, i) => rScale(d.value) * sin(angleSlice * i - HALF_PI))
.style("fill", "none")
.style("pointer-events", "all")
.on("mouseover", function(d, i) {
tooltip
.attr('x', this.cx.baseVal.value - 10)
.attr('y', this.cy.baseVal.value - 10)
.transition()
.style('display', 'block')
.text(Format(d.value) + cfg.unit);
})
.on("mouseout", function() {
tooltip.transition()
.style('display', 'none').text('');
});
const tooltip = g.append("text")
.attr("class", "tooltip")
.attr('x', 0)
.attr('y', 0)
.style("font-size", "12px")
.style('display', 'none')
.attr("text-anchor", "middle")
.attr("dy", "0.35em");
if (cfg.legend !== false && typeof cfg.legend === "object") {
let legendZone = svg.append('g');
let names = data.map(el => el.name);
if (cfg.legend.title) {
let title = legendZone.append("text")
.attr("class", "title")
.attr('transform', `translate(${cfg.legend.translateX},${cfg.legend.translateY})`)
.attr("x", cfg.w - 70)
.attr("y", 10)
.attr("font-size", "12px")
.attr("fill", "#404040")
.text(cfg.legend.title);
}
let legend = legendZone.append("g")
.attr("class", "legend")
.attr("height", 100)
.attr("width", 200)
.attr('transform', `translate(${cfg.legend.translateX},${cfg.legend.translateY + 20})`);
// Create rectangles markers
legend.selectAll('rect')
.data(names)
.enter()
.append("rect")
.attr("x", cfg.w - 65)
.attr("y", (d, i) => i * 20)
.attr("width", 10)
.attr("height", 10)
.style("fill", (d, i) => cfg.color(i));
// Create labels
legend.selectAll('text')
.data(names)
.enter()
.append("text")
.attr("x", cfg.w - 52)
.attr("y", (d, i) => i * 20 + 9)
.attr("font-size", "11px")
.attr("fill", "#737373")
.text(d => d);
}
return svg;
}
//////////////////////////////////////////////////////////////
//////////////////////// Set-Up //////////////////////////////
//////////////////////////////////////////////////////////////
var marginRadar = {
top: 150,
right: 80,
bottom: 50,
left: 80
},
widthRadar = Math.min(700, window.innerWidth / 4) - marginRadar.left - marginRadar.right,
heightRadar = Math.min(widthRadar, window.innerHeight - marginRadar.top - marginRadar.bottom);
//////////////////////////////////////////////////////////////
////////////////////////// Data //////////////////////////////
//////////////////////////////////////////////////////////////
var radarChartOptions = {
w: 290,
h: 350,
margin: marginRadar,
levels: 6,
roundStrokes: false,
color: d3.scaleOrdinal().range(["#AFC52F", "#ff6600"]),
format: '.0f',
legend: {
title: 'Legend',
translateX: 150,
translateY: 100
},
unit: '%'
};
// Just some example JSON data I found at https://support.oneskyapp.com/hc/en-us/articles/208047697-JSON-sample-files
d3.json('https://support.oneskyapp.com/hc/en-us/article_attachments/202761727/example_2.json', function(fakeData) {
// Which I then map into your format - you don't need to do this
const data = Object.entries(fakeData['quiz']).map(([topic, questionObject]) => ({
name: topic,
axes: [
{ axis: 'difficulty', value: Object.keys(questionObject).length },
{ axis: 'depth', value: Object.keys(questionObject).length },
{ axis: 'duration', value: Object.keys(questionObject).length },
{ axis: 'delicacy', value: Object.keys(questionObject).length }
]
}));
// Draw the chart, get a reference the created svg element :
let svg_radar = RadarChart(".radarChart", data, radarChartOptions);
});
<head>
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-path.v1.min.js" charset="utf-8"></script>
<!-- source: https://gist.github.com/mthh/7e17b680b35b83b49f1c22a3613bd89f -->
</head>
<body>
<table>
<tr>
<td style="radarChart">
<div class="radarChart" style="display: inline-flex;"></div>
</td>
</tr>
</table>