Skip to content

Instantly share code, notes, and snippets.

@Zhenmao
Created March 31, 2018 23:23
Show Gist options
  • Save Zhenmao/e82798d65c0d083745e58184bcc6d291 to your computer and use it in GitHub Desktop.
Save Zhenmao/e82798d65c0d083745e58184bcc6d291 to your computer and use it in GitHub Desktop.
Parallel coordinates
<!DOCTYPE html>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="parallel-coordinates.css">
<body>
<div id="chart"></div>
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="parallel-coordinates.js"></script>
<script>
var divID = "#chart";
var datafile = "user1.csv"; // Change to "user2.csv" or "user3.csv" to switch dataset
parcoords(divID, datafile);
</script>
body {
background: #222222;
}
svg {
font: 12px sans-serif;
}
svg text {
fill: #B6B6B6;
}
.background path {
fill: none;
stroke: #ddd;
shape-rendering: crispEdges;
}
.foreground path {
fill: none;
stroke-width: 1.5px;
/* stroke: steelblue; */
}
.brush .extent {
fill-opacity: .3;
stroke: #fff;
shape-rendering: crispEdges;
}
.axis line,
.axis path {
fill: none;
stroke: #B6B6B6;
shape-rendering: crispEdges;
}
.axis-title {
font-size: 12px;
}
.axis text {
text-shadow: 0 1px 0 #222222, 1px 0 0 #222222, 0 -1px 0 #222222, -1px 0 0 #222222;
}
.slider-track,
.slider-handle {
fill: #B6B6B6;
}
var parcoords = function(id, datafile) {
var margin = { top: 30, right: 20, bottom: 50, left: 60 },
width = 800 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scalePoint().rangeRound([0, width]).padding(0),
y = {};
var line = d3.line(),
axis = d3.axisLeft();
var dataByHour;
var dimensions;
var currentHour = 0;
var hours = d3.range(24);
var svg = d3.select(id).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var background = svg.append("g")
.attr("class", "background");
var foreground = svg.append("g")
.attr("class", "foreground");
d3.csv(datafile, function(error, data) {
dataByHour = d3.nest()
.key(function(d) {
return d.Hour;
})
.map(data);
// Extract the list of dimensions and create a scale for each.
dimensions = d3.keys(data[0]).filter(function (d) {
return d != "Hour" && d != "Color" && (y[d] = d3.scaleLinear()
.domain(d3.extent(data, function (p) { return +p[d]; }))
.range([height, 0]));
});
x.domain(dimensions);
// Add a group element for each dimension.
var g = svg.selectAll(".dimension")
.data(dimensions)
.enter().append("g")
.attr("class", "dimension")
.attr("transform", function (d) { return "translate(" + x(d) + ")"; });
// Add an axis and title.
g.append("g")
.attr("class", "axis")
.each(function (d) { d3.select(this).call(axis.scale(y[d])); })
.append("text")
.attr("class", "axis-title")
.style("text-anchor", "middle")
.attr("y", -9)
.text(function (d) { return d; });
// Add and store a brush for each axis.
g.append("g")
.attr("class", "brush")
.each(function (d) {
d3.select(this).call(
y[d].brush = d3.brushY()
.extent([[-8, 0], [8, height]])
.on("brush", brush)
)
})
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
addSlider();
drawLines();
});
function addSlider() {
var sliderWidth = width, // Total width of the slider
sliderHeight = 4, // The height of the slider color rect
sliderLeft = 0, // The left positiion of the slider
sliderTop = 25, // The top position of the slider
handleRadius = 8; // The radius of slider circle handle
// X scale for slider
var x = d3.scaleLinear()
.domain([0, 23])
.range([0, sliderWidth])
.clamp(true);
// Slider container
var slider = svg.append("g")
.attr("class", "slider axis")
.attr("transform", "translate(" + sliderLeft + "," + (height + sliderTop) + ")");
// Slider title
slider.append("text")
.attr("class", "slider-caption")
.attr("x", 0)
.attr("y", -9)
.text("Hour");
// Slider ticks
var xAxis = d3.axisBottom(x)
.tickSize(13)
.tickFormat(d3.format("d"))
.tickValues(hours);
slider.call(xAxis)
.select(".domain")
.remove(); // Remove the domain line
// Slider circle handle
var handle = slider.append("circle")
.attr("class", "slider-handle")
.attr("cx", x(currentHour))
.attr("cy", sliderHeight / 2)
.attr("r", handleRadius);
// Slider track
slider.append("rect")
.attr("class", "slider-track")
.attr("height", sliderHeight)
.attr("width", sliderWidth)
.call(d3.drag()
.on("start drag", dragging)
.on("end", drawLines));
function dragging() {
// Find the year
currentHour = Math.round(x.invert(d3.event.x));
// Transition the handle
handle.transition()
.duration(50)
.ease(d3.easeLinear)
.attr("cx", x(currentHour));
}
}
function drawLines() {
var data = dataByHour.get(currentHour);
// Add grey background lines for context.
bgPath = background
.selectAll("path")
.data(data);
bgPath = bgPath.enter()
.append("path")
.merge(bgPath)
.transition()
.attr("d", path);
// Add blue foreground lines for focus.
fgPath = foreground
.selectAll("path")
.data(data);
fgPath = fgPath.enter()
.append("path")
.merge(fgPath)
.transition()
.attr("d", path)
.attr("stroke", function(d) {
return d.Color;
});
}
// Returns the path for a given data point.
function path(d) {
return line(dimensions.map(function (p) { return [x(p), y[p](d[p])]; }));
}
// Handles a brush event, toggling the display of foreground lines.
function brush() {
var actives = [],
extents = [];
svg.selectAll(".brush").each(function(p, i) {
var extent = d3.brushSelection(this);
if (extent) {
actives.push(dimensions[i]);
extents.push(extent.map(y[dimensions[i]].invert));
}
});
fgPath.style("display", function (d) {
return actives.every(function (p, i) {
return extents[i][1] <= d[p] && d[p] <= extents[i][0];
}) ? null : "none";
});
}
}
Hour Satisfaction Violation Pattern Audit Log Modification Access Pattern Data Exfiltration Privilege Change Color
0 0 0 0 0 0 0 green
1 0 0 0 0 0 0 green
2 0 47 51 32 54 1 orange
3 0 93 57 86 98 44 red
4 0 89 53 87 73 57 red
5 0 93 79 82 49 22 red
6 0 22 34 18 32 3 green
7 2 7 1 20 0 0 green
8 5 5 0 13 6 0 green
9 15 24 0 16 12 1 green
10 35 22 2 34 24 0 green
11 67 56 3 33 23 15 orange
12 78 35 0 54 15 8 orange
13 0 0 0 0 0 0 green
14 22 43 24 12 45 12 green
15 31 4 22 4 4 19 green
16 16 3 12 3 19 25 green
17 66 35 15 76 22 5 orange
18 3 65 0 89 56 2 orange
19 9 22 0 0 0 0 green
20 2 15 13 0 0 0 green
21 0 0 2 0 0 0 green
22 0 0 0 0 0 0 green
23 0 0 0 0 0 0 green
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment