/*-
* ============LICENSE_START=======================================================
* Copyright (C) 2020 Nordix Foundation.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* ============LICENSE_END=========================================================
*/
import * as d3 from "d3";
/*
* Create a chart and append it to a container
*/
function createChart(data, container, title, unit, lineStroke, nodeColour) {
// Set the dimensions of the canvas
var margin = {
top : 30,
right : 20,
bottom : 30,
left : 50
}, width = 600 - margin.left - margin.right, height = 270 - margin.top
- margin.bottom;
// Set the ranges
var x = d3.time.scale().range([ 0, width ]);
var y = d3.scale.linear().range([ height, 0 ]);
// Define the axes
var xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(5).innerTickSize(
-height).outerTickSize(0).tickPadding(10);
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(10).innerTickSize(
-width).outerTickSize(0).tickPadding(10);
// Define the line
var valueline = d3.svg.line().x(function(d) {
return x(d.timestamp);
}).y(function(d) {
return y(d.value);
});
// Add the svg canvas to the container
var svg = d3.select(container).append("svg").attr("preserveAspectRatio",
"xMinYMin meet").attr("viewBox", "0 0 600 400").classed(
"svg-content-responsive", true).append("g").attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Set the unit for the value
svg.attr("unit", unit);
// Format the data for the chart
data.forEach(function(d) {
d.timestamp = d.timestamp;
d.value = +d.value;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.timestamp;
}));
y.domain([ 0, d3.max(data, function(d) {
return Math.ceil((d.value + 1) / 10) * 10;
}) ]);
// Set the colour of the line
if (!lineStroke) {
lineStroke = "#5fbadd"
}
// Set the colour of the circles
if (!nodeColour) {
nodeColour = "#00A9D4"
}
// Add the valueline path
svg.append("path").attr("class", "line").data(data).attr("unit", unit)
.attr("stroke", lineStroke).attr("d", valueline(data));
// Add the scatterplot
svg.selectAll("circle").data(data).enter().append("circle").attr("r", 3.5)
.attr("class", "circle").attr("fill", nodeColour).attr("cx",
function(d) {
return x(d.timestamp);
}).attr("cy", function(d) {
return y(d.value);
})
// Apply the tooltip to each node
.on(
"mouseover",
function(d) {
d3.select("body").select(".tooltip").transition()
.duration(50).style("opacity", 1);
d3.select("body").select(".tooltip").html(
formatDate(new Date(d.timestamp)) + "
"
+ d.value + (unit ? " " + unit : ""))
.style("left", (d3.event.pageX) + "px").style(
"top", (d3.event.pageY - 28) + "px");
}).on(
"mouseout",
function(d) {
d3.select("body").select(".tooltip").transition()
.duration(500).style("opacity", 0);
});
// Add the X Axis
svg.append("g").attr("class", "x axis").attr("transform",
"translate(0," + height + ")").call(xAxis);
// Add the Y Axis
svg.append("g").attr("class", "y axis").call(yAxis);
// Add the title
svg.append("text").attr("x", (width / 2)).attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle").style("font-size", "16px").style(
"text-decoration", "underline").text(title);
// Add the background
svg.selectAll(".tick:not(:first-of-type) line").attr("stroke", "#777")
.attr("stroke-dasharray", "2,2");
}
/*
* Generates random chart data. Used when initializing the charts so that they
* are not empty on load
*/
function generateRandomData() {
var data = [];
for (var i = 0; i < 30; i++) {
data.push({
timestamp : new Date().getTime() - (i * 5000),
value : Math.floor(Math.random() * 100) + 1
});
}
return data;
}
/*
* Update a chart belonging to a specific container
*/
function updateChart(container, data, nodeColour) {
var margin = {
top : 30,
right : 20,
bottom : 30,
left : 50
}, width = 600 - margin.left - margin.right, height = 270 - margin.top
- margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse;
// Format the data for the chart
data.forEach(function(d) {
d.timestamp = d.timestamp;
d.value = +d.value;
});
// Select the chart
var svg = d3.select(container);
// Set the ranges
var x = d3.time.scale().range([ 0, width ]);
var y = d3.scale.linear().range([ height, 0 ]);
// Define the axes
var xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(5).innerTickSize(
-height).outerTickSize(0).tickPadding(10);
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(10).innerTickSize(
-width).outerTickSize(0).tickPadding(10);
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.timestamp;
}));
y.domain([ 0, d3.max(data, function(d) {
return Math.ceil((d.value + 1) / 10) * 10;
}) ]);
// Update the valueline path
var valueline = d3.svg.line().x(function(d) {
return x(d.timestamp);
}).y(function(d) {
return y(d.value);
});
var unit = svg.select(".line").attr("unit");
// Remove all nodes
svg.selectAll("circle").remove();
// Set the node colour if one is passed in
if (!nodeColour) {
nodeColour = "#00A9D4"
}
// Make the changes
svg.select(".line").data(data) // change the line
.transition().duration(750).attr("d", valueline(data));
svg.select(".x.axis") // change the x axis
.transition().duration(750).call(xAxis.ticks(5));
svg.select(".y.axis") // change the y axis
.transition().duration(750).call(yAxis);
// Redraw the nodes based on the new data
svg.select("svg").select("g").selectAll("circle").data(data).enter()
.append("circle").attr("r", 3.5).attr("class", "circle").attr(
"fill", nodeColour).attr("cx", function(d) {
return x(d.timestamp);
}).attr("cy", function(d) {
return y(d.value);
})
// Apply the tooltip to each node
.on(
"mouseover",
function(d) {
d3.select("body").select(".tooltip").transition()
.duration(50).style("opacity", 1);
d3.select("body").select(".tooltip").html(
formatDate(new Date(d.timestamp)) + "
"
+ d.value + (unit ? " " + unit : ""))
.style("left", (d3.event.pageX) + "px").style(
"top", (d3.event.pageY - 28) + "px");
}).on(
"mouseout",
function(d) {
d3.select("body").select(".tooltip").transition()
.duration(500).style("opacity", 0);
});
}
/*
* Initialize a singleton div used as a floating tooltip for all charts
*/
function initTooltip() {
d3.select("body").append("div").attr("class", "tooltip").attr("id",
"tooltip").style("opacity", 0);
}
/*
* Format a date object to string
*/
function formatDate(date) {
return date.toLocaleString().replace(',', '');
}
export { initTooltip, createChart, updateChart, generateRandomData };