show multiple datasets

This commit is contained in:
2024-12-06 15:30:07 +08:00
parent 857e1262ea
commit 156c71c2ff
3 changed files with 129 additions and 347 deletions

View File

@@ -2,28 +2,13 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>D3.js Multiple Line Chart</title>
<title>D3.js Line Chart - Local File with Point Details</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style> <style>
.line { body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
fill: none; .line { fill: none; stroke-width: 2px; }
stroke: steelblue; .axis { font-size: 12px; }
stroke-width: 2px; .legend { font-size: 12px; }
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.dot {
fill: steelblue;
stroke: #fff;
}
.tooltip { .tooltip {
position: absolute; position: absolute;
text-align: center; text-align: center;
@@ -39,28 +24,22 @@
</style> </style>
</head> </head>
<body> <body>
<h1>D3.js Line Chart from Local JSON File with Point Details</h1> <input type="file" id="fileInput" />
<input type="file" id="fileInput">
<div id="chart"></div> <div id="chart"></div>
<script> <script>
// Set the dimensions and margins of the graph // Sample multiple dataset
const margin = {top: 20, right: 20, bottom: 30, left: 50}; const data = [
const width = 960 - margin.left - margin.right; {"lux":10.0,"timestamp":442470841810000},
const height = 500 - margin.top - margin.bottom; {"lux":20.0,"timestamp":442471010509000},
{"lux":30.0,"timestamp":442471182576000},
// Parse the date / time {"lux":40.0,"timestamp":442471360117000},
const parseTime = d3.timeParse("%Y-%m-%d"); {"lux":100.0,"timestamp":442471522264000}
const formatTime = d3.timeFormat("%Y-%m-%d"); ];
const colorMap = new Map();
// Set the ranges colorMap.set('original', 'steelblue');
const x = d3.scaleLinear().range([0, width]); colorMap.set('fastLuxData', 'green');
const y = d3.scaleLinear().range([height, 0]); colorMap.set('slowLuxData', 'red');
// Define the line
const valueline = d3.line()
.x(d => x(d.timestamp))
.y(d => y(d.lux));
function weightIntegral(x) { function weightIntegral(x) {
return x * (x * 0.5 + 4000000000); return x * (x * 0.5 + 4000000000);
@@ -80,8 +59,8 @@
return sum / totalWeight; return sum / totalWeight;
} }
// Function to create the chart // Render multiple line chart
function createChart(data) { function renderMultiLineChart(data) {
var timestampNow; var timestampNow;
var fastLuxData = []; var fastLuxData = [];
var slowLuxData = []; var slowLuxData = [];
@@ -114,115 +93,141 @@
datasets.push(slowLuxDataObj); datasets.push(slowLuxDataObj);
console.log(datasets.length); console.log(datasets.length);
console.log(datasets[0]); console.log(datasets[0]);
datasets = JSON.parse(JSON.stringify(datasets));
// Clear previous chart if any // Clear previous chart
d3.select("#chart").html(""); d3.select("#chart").html("");
// Append the svg object to the body of the page // Chart dimensions
const svg = d3.select("#chart").append("svg") const margin = {top: 50, right: 50, bottom: 50, left: 50};
.attr("width", width + margin.left + margin.right) const width = 800 - margin.left - margin.right;
.attr("height", height + margin.top + margin.bottom) const height = 400 - margin.top - margin.bottom;
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// Create tooltip // Tooltip
const tooltip = d3.select("body").append("div") const tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip") .attr("class", "tooltip")
.style("opacity", 0); .style("opacity", 0);
// Format the data // Create SVG
/*fastLuxData.forEach(function(d) { const svg = d3.select("#chart")
d.timestamp = d.timestamp;//parseTime(d.timestamp); .append("svg")
d.lux = +d.lux; .attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// Parse dates
const parseTime = d3.timeParse("%Y-%m-%d");
const formatTime = d3.timeFormat("%Y-%m-%d");
/*datasets.forEach(series => {
series.values.forEach(d => {
d.timestamp = parseTime(d.timestamp);
});
});*/ });*/
// Scale the range of the data // Combine all values for scale
// Scale the range of the data const allValues = datasets.flatMap(series => series.values.map(d => d.lux));
x.domain(d3.extent(datasets[0].values.flatMap(d => d.timestamp), d => d.timestamp));
y.domain([0, d3.max(datasets[0].values.flatMap(d => d.lux), d => d.lux)]);
// Add the valueline paths // Scales
const dataset = svg.selectAll(".dataset") const x = d3.scaleLinear()
.data(datasets) .domain(d3.extent(datasets[0].values, d => d.timestamp))
.enter().append("g") .range([0, width]);
.attr("class", "dataset");
dataset.append("path")
.attr("class", "line")
.attr("d", d => valueline(d.values))
.style("stroke", d => color(d.name));
// Add the X Axis const y = d3.scaleLinear()
.domain([
d3.min(allValues) * 0.9,
d3.max(allValues) * 1.1
])
.range([height, 0]);
// Line generator
const line = d3.line()
.x(d => x(d.timestamp))
.y(d => y(d.lux));
// X-axis
svg.append("g") svg.append("g")
.attr("class", "axis")
.attr("transform", `translate(0,${height})`) .attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x)); .call(d3.axisBottom(x));
// Add the Y Axis // Y-axis
svg.append("g") svg.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y)); .call(d3.axisLeft(y));
// Add the dots // Lines and data points for each dataset
dataset.each(function(dataset) { datasets.forEach(series => {
d3.select(this).selectAll(".dot") // Line
.data(dataset.values) svg.append("path")
.datum(series.values)
.attr("class", "line")
.attr("fill", "none")
.attr("stroke", colorMap.get(series.name))
.attr("stroke-width", 2)
.attr("d", line);
// Data points
svg.selectAll(`.dot-${series.name}`)
.data(series.values)
.enter().append("circle") .enter().append("circle")
.attr("class", "dot") .attr("class", `dot dot-${series.name}`)
.attr("r", 5) .attr("cx", d => x(d.timestamp))
.attr("cx", d => x(d.timestamp)) .attr("cy", d => y(d.lux))
.attr("cy", d => y(d.lux)) .attr("r", 5)
.style("fill", color(dataset.name)) .attr("fill", colorMap.get(series.name))
.on("mouseover", function(event, d) { .on("mouseover", function(event, d) {
tooltip.transition() tooltip.transition()
.duration(200) .duration(200)
.style("opacity", .9); .style("opacity", .9);
tooltip.html(`${dataset.name}<br/>timestamp: ${d.timestamp}<br/>lux: ${d.lux}`) tooltip.html(`${series.name}<br/>timestamp: ${d.timestamp}<br/>lux: ${d.lux}`)
.style("left", (event.pageX + 5) + "px") .style("left", (event.pageX + 5) + "px")
.style("top", (event.pageY - 28) + "px"); .style("top", (event.pageY - 28) + "px");
}) })
.on("mouseout", function(d) { .on("mouseout", function(d) {
tooltip.transition() tooltip.transition()
.duration(500) .duration(500)
.style("opacity", 0); .style("opacity", 0);
}); });
}); });
// Add a legend // Legend
const legend = svg.selectAll('.legend') const legend = svg.selectAll(".legend")
.data(datasets) .data(datasets)
.enter().append('g') .enter().append("g")
.attr('class', 'legend') .attr("class", "legend")
.attr('transform', (d, i) => `translate(0,${i * 20})`); .attr("transform", (d, i) => `translate(${width + 10},${i * 20})`);
legend.append('rect') legend.append("rect")
.attr('x', width + 5) .attr("x", 0)
.attr('width', 18) .attr("width", 10)
.attr('height', 18) .attr("height", 10)
.style('fill', d => color(d.name)); .style("fill", d => colorMap.get(d.name));
legend.append('text') legend.append("text")
.attr('x', width + 25) .attr("x", 20)
.attr('y', 9) .attr("y", 9)
.attr('dy', '.35em') .text(d => d.name)
.style('text-anchor', 'start') .style("text-anchor", "start");
.text(d => d.name);
} }
// File input event listener // Render chart
renderMultiLineChart(data);
// File input handler
document.getElementById('fileInput').addEventListener('change', function(e) { document.getElementById('fileInput').addEventListener('change', function(e) {
const file = e.target.files[0]; const file = e.target.files[0];
if (file) { const reader = new FileReader();
const reader = new FileReader();
reader.onload = function(e) { reader.onload = function(event) {
const contents = e.target.result; try {
try { const jsonData = JSON.parse(event.target.result);
const data = JSON.parse(contents); renderMultiLineChart(jsonData);
createChart(data); } catch (error) {
} catch (error) { alert('Error parsing JSON file: ' + error);
console.error("Error parsing JSON:", error); }
alert("Error parsing JSON file. Please make sure it's a valid JSON."); };
}
}; reader.readAsText(file);
reader.readAsText(file);
}
}); });
</script> </script>
</body> </body>

View File

@@ -1,188 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3.js Multiple Line Chart</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.line { fill: none; stroke-width: 2px; }
.axis { font-size: 12px; }
.legend { font-size: 12px; }
</style>
</head>
<body>
<input type="file" id="fileInput" accept=".json" />
<div id="chart"></div>
<script>
// Sample multiple dataset
const data = [
{
name: 'Temperature',
color: 'steelblue',
values: [
{date: '2023-01-01', value: 32},
{date: '2023-02-01', value: 35},
{date: '2023-03-01', value: 40},
{date: '2023-04-01', value: 45},
{date: '2023-05-01', value: 50}
]
},
{
name: 'Rainfall',
color: 'green',
values: [
{date: '2023-01-01', value: 10},
{date: '2023-02-01', value: 15},
{date: '2023-03-01', value: 20},
{date: '2023-04-01', value: 25},
{date: '2023-05-01', value: 30}
]
}
];
// Render multiple line chart
function renderMultiLineChart(datasets) {
// Clear previous chart
d3.select("#chart").html("");
// Chart dimensions
const margin = {top: 50, right: 100, bottom: 50, left: 50};
const width = 700 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
// Tooltip
const tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// Create SVG
const svg = d3.select("#chart")
.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})`);
// Parse dates
const parseTime = d3.timeParse("%Y-%m-%d");
const formatTime = d3.timeFormat("%Y-%m-%d");
datasets.forEach(series => {
series.values.forEach(d => {
d.date = parseTime(d.date);
});
});
// Combine all values for scale
const allValues = datasets.flatMap(series => series.values.map(d => d.value));
// Scales
const x = d3.scaleTime()
.domain(d3.extent(datasets[0].values, d => d.date))
.range([0, width]);
const y = d3.scaleLinear()
.domain([
d3.min(allValues) * 0.9,
d3.max(allValues) * 1.1
])
.range([height, 0]);
// Line generator
const line = d3.line()
.x(d => x(d.date))
.y(d => y(d.value));
// X-axis
svg.append("g")
.attr("class", "axis")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x));
// Y-axis
svg.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y));
// Lines and data points for each dataset
datasets.forEach(series => {
// Line
svg.append("path")
.datum(series.values)
.attr("class", "line")
.attr("fill", "none")
.attr("stroke", series.color)
.attr("stroke-width", 2)
.attr("d", line);
// Data points
svg.selectAll(`.dot-${series.name}`)
.data(series.values)
.enter().append("circle")
.attr("class", `dot dot-${series.name}`)
.attr("cx", d => x(d.date))
.attr("cy", d => y(d.value))
.attr("r", 5)
.attr("fill", series.color)
.on("mouseover", (event, d) => {
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(`
<strong>${series.name}</strong><br>
Date: ${formatTime(d.date)}<br>
Value: ${d.value}
`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 28) + "px");
})
.on("mouseout", () => {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
});
// Legend
const legend = svg.selectAll(".legend")
.data(datasets)
.enter().append("g")
.attr("class", "legend")
.attr("transform", (d, i) => `translate(${width + 10},${i * 20})`);
legend.append("rect")
.attr("x", 0)
.attr("width", 10)
.attr("height", 10)
.style("fill", d => d.color);
legend.append("text")
.attr("x", 20)
.attr("y", 9)
.text(d => d.name)
.style("text-anchor", "start");
}
// Render chart
renderMultiLineChart(data);
// File input handler
document.getElementById('fileInput').addEventListener('change', function(e) {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = function(event) {
try {
const jsonData = JSON.parse(event.target.result);
renderMultiLineChart(jsonData);
} catch (error) {
alert('Error parsing JSON file: ' + error);
}
};
reader.readAsText(file);
});
</script>
</body>
</html>

View File

@@ -1,35 +0,0 @@
[
{
"name": "Temperature",
"color": "steelblue",
"values": [
{"date": "2023-01-01", "value": 32},
{"date": "2023-02-01", "value": 35},
{"date": "2023-03-01", "value": 40},
{"date": "2023-04-01", "value": 45},
{"date": "2023-05-01", "value": 50}
]
},
{
"name": "Temperature2",
"color": "red",
"values": [
{"date": "2023-01-01", "value": 35},
{"date": "2023-02-01", "value": 40},
{"date": "2023-03-01", "value": 45},
{"date": "2023-04-01", "value": 50},
{"date": "2023-05-01", "value": 32}
]
},
{
"name": "Temperature3",
"color": "green",
"values": [
{"date": "2023-01-01", "value": 40},
{"date": "2023-02-01", "value": 45},
{"date": "2023-03-01", "value": 50},
{"date": "2023-04-01", "value": 32},
{"date": "2023-05-01", "value": 35}
]
}
]