Skip to content

Instantly share code, notes, and snippets.

@oivoodoo
Created December 4, 2018 07:16
Show Gist options
  • Save oivoodoo/1affe7683aaf618653707a4724e4c7e5 to your computer and use it in GitHub Desktop.
Save oivoodoo/1affe7683aaf618653707a4724e4c7e5 to your computer and use it in GitHub Desktop.
D3 chart example
// D3 Chart Example
import "../styles/index.scss";
import * as d3 from "d3";
d3.csv("/public/data/File.csv", function(d) {
return { value: +d.NumericColumnB, name: d.StringColumnA };
}).then(function(data) {
// sort data by value
data.sort(function(a, b) {
return a.value - b.value;
});
const svgMainWidth = 1060,
svgMainHeight = 960;
// Edit this to change the context to focus proportions
const contextScale = 1 / 6;
const margin = { top: 10, right: 10, bottom: 20 };
const margin2 = { top: 10, bottom: 20, left: 40 };
const combinedRenderWidth =
svgMainWidth - margin.right * 2 - margin2.left * 2 - 10;
margin2.right =
svgMainWidth -
Math.round(combinedRenderWidth * contextScale) -
margin2.left;
margin.left = svgMainWidth - margin2.right + 40 + 10;
const width = svgMainWidth - margin.left - margin.right,
width2 = svgMainWidth - margin2.left - margin2.right,
height = svgMainHeight - margin.top - margin.bottom;
// this div is used for tooltips
const tooltipDiv = d3
.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// scales
const x = d3.scaleLinear().range([0, width]);
const y = d3
.scaleBand()
.rangeRound([height, 0])
.paddingInner(0.1);
const x2 = d3.scaleLinear().range([0, width2]);
const y2 = d3
.scaleBand()
.rangeRound([height, 0])
.paddingInner(0.1);
const textScale = d3
.scaleLinear()
.domain([15, 50])
.range([20, 6])
.clamp(true);
const yZoom = d3
.scaleLinear()
.range([0, height])
.domain([0, height]);
const xAxis = d3.axisBottom(x);
const yAxis = d3.axisLeft(y);
const yAxis2 = d3.axisLeft(y2).tickValues([]);
const svg = d3
.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
//Add the clip path for the main bar chart
svg
.append("defs")
.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width + margin.left)
.attr("height", height);
// focus is the main bar chart
const focus = svg
.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.append("g")
.attr("class", "clip-class");
// context is the minimap area
const context = svg
.append("g")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
const brush = d3
.brushY()
.extent([[0, 0], [width2, height]])
// .extent([[0, y2.range()[0]], [width2, y2.range()[1]]])
.on("brush", brushmove);
x.domain(
d3.extent(data, function(d) {
return d.value;
})
).nice();
y.domain(
data.map(function(d) {
return d.name;
})
);
x2.domain(x.domain());
y2.domain(y.domain());
focus
.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.attr("class", function(d) {
return "bar bar--" + (d.value < 0 ? "negative" : "positive");
})
.attr("x", function(d) {
return x(Math.min(0, d.value));
})
.attr("y", function(d) {
return y(d.name);
})
.attr("width", function(d) {
return Math.abs(x(d.value) - x(0));
})
.attr("height", y.bandwidth())
// tooltip
.on("mouseover", function(d) {
tooltipDiv
.transition()
.duration(200)
.style("opacity", 1.0);
tooltipDiv
.html("Value: " + d.value)
.style("left", d3.event.pageX + "px")
.style("top", d3.event.pageY - 28 + "px");
})
.on("mouseout", function(d) {
tooltipDiv
.transition()
.duration(500)
.style("opacity", 0);
});
svg
.select(".focus")
.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
const negativeTicks = focus
.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + x(0) + ",0)")
.call(yAxis)
.selectAll(".tick")
.filter(function(d, i) {
return data[i].value < 0;
});
negativeTicks.select("line").attr("x2", 6);
negativeTicks
.select("text")
.attr("x", 9)
.style("text-anchor", "start");
context
.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.attr("class", function(d) {
return "bar bar--" + (d.value < 0 ? "negative" : "positive");
})
.attr("x", function(d) {
return x2(Math.min(0, d.value));
})
.attr("y", function(d) {
return y2(d.name);
})
.attr("width", function(d) {
return Math.abs(x2(d.value) - x2(0));
})
.attr("height", y2.bandwidth());
context
.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + x2(0) + ",0)")
.call(yAxis2);
context
.append("g")
.attr("class", "y brush")
.call(brush)
.call(brush.move, [0, height / 3]) // initial brush position
.selectAll("rect")
.attr("x", 0)
.attr("width", width2);
//Function runs on a brush move - to update the big bar chart
function updateMainChart() {
const bars = focus.selectAll(".bar");
//UPDATE
bars
.attr("y", function(d) {
return y(d.name);
})
.attr("height", y.bandwidth())
.attr("x", function(d) {
return x(Math.min(0, d.value));
})
.attr("width", function(d) {
return Math.abs(x(d.value) - x(0));
});
const negativeTicks = focus
.select(".y.axis")
.selectAll(".tick")
.filter(function(d, i) {
return data[i].value < 0;
});
negativeTicks.select("line").attr("x2", 6);
negativeTicks
.select("text")
.attr("x", 9)
.style("text-anchor", "start");
// EXIT
bars.exit().remove();
}
//First function that runs on a brush move
function brushmove() {
const selection = d3.event.selection;
//Which bars are in selection
const selected = y2.domain().filter(function(d) {
return selection[0] - y2.bandwidth() <= y2(d) && y2(d) <= selection[1];
});
//Update the labels size
d3.selectAll(".y.axis text").style("font-size", textScale(selected.length));
const originalRange = yZoom.range();
yZoom.domain(selection);
y.domain(
data.map(function(d) {
return d.name;
})
);
y.rangeRound([yZoom(originalRange[1]), yZoom(originalRange[0])])
.paddingInner(0.4)
.paddingOuter(0);
//Update the y axis of the big chart
focus.select(".y.axis").call(yAxis);
updateMainChart();
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment