Commit b9cb1e08 authored by Almouhannad Hafez's avatar Almouhannad Hafez

Add scatter plot

parent 0a8a1501
This diff is collapsed.
......@@ -8,6 +8,14 @@
</head>
<body>
<div id="scatterplot-container">
<svg id="scatterplot"></svg>
<ul class="legend" id="scatterplot-legend">
<li><span class="legend-e easy" id="scatterplot-legend-easy"></span> Easy</li>
<li><span class="legend-e intermediate" id="scatterplot-intermediate"></span> Intermediate</li>
<li><span class="legend-e difficult" id="scatterplot-difficult"></span> Difficult</li>
</ul>
</div>
<script type="module" src="/src/ts/main.ts"></script>
</body>
......
......@@ -5,8 +5,87 @@ body {
padding: 0;
box-sizing: border-box;
overflow-x: hidden;
background: #f7f7f7;
}
h1 {
color: red;
}
\ No newline at end of file
/* #region scatterplot*/
#scatterplot-container {
padding: 1em;
}
#scatterplot-container .source {
font-size: 10px;
color: #888;
}
#scatterplot-container .source a {
color: #888;
}
/* Axes */
#scatterplot-container .axis line {
fill: none;
stroke: #ddd;
shape-rendering: crispEdges;
}
#scatterplot-container .axis text {
font-size: 13px;
fill: #6b6b6b;
}
#scatterplot-container .axis-title {
font-size: 13px;
fill: #888;
}
.y-axis .tick:first-child line {
stroke: #b1b1b1;
}
#scatterplot-container .y-axis .tick:first-child text {
display: none;
}
#scatterplot-container .x-axis .tick:first-child line {
display: none;
}
#scatterplot-container .axis path {
display: none;
}
/* Legend */
#scatterplot-container .legend {
margin: 20px 0;
list-style: none;
padding: 0;
}
#scatterplot-container .legend li {
display: inline-block;
margin: 0 20px 0 0;
}
#scatterplot-container .legend-e {
width: 12px;
height: 12px;
margin-right: 3px;
display: inline-block;
}
#scatterplot-container .legend-e.easy {
background: #d3eecd;
}
#scatterplot-container .legend-e.intermediate {
background: #7bc77e;
}
#scatterplot-container .legend-e.difficult {
background: #2a8d46;
}
/* #endregion */
\ No newline at end of file
import '../css/style.css';
import { ChartConfiguration } from './chartConfiguration';
import { Scatterplot } from './scatter';
import '/src/css/style.css';
import * as d3 from 'd3';
d3.select("body")
.append("h1")
.text("Hello world");
/**
* Load data from CSV file asynchronously and render scatter plot
*/
const scatterplot = new Scatterplot(new ChartConfiguration("#scatterplot"));
d3.csv('/data/vancouver_trails.csv')
.then(data => {
// Convert strings to numbers
data.forEach((d: any) => {
d.time = +d.time;
d.distance = +d.distance;
});
// Initialize chart
scatterplot.data = data;
// Show chart
scatterplot.updateVis();
})
.catch(error => console.error(error));
setTimeout(() => {
scatterplot.data = [];
scatterplot.updateVis();
}, 1000);
import * as d3 from 'd3';
import { Chart } from "./chart";
import { ChartConfiguration } from './chartConfiguration';
export class Scatterplot extends Chart {
private colorScale: d3.ScaleOrdinal<string, unknown, never>;
private xScale: d3.ScaleLinear<number, number, never>;
private yScale: d3.ScaleLinear<number, number, never>;
private xAxis: d3.Axis<d3.NumberValue>;
private yAxis: d3.Axis<d3.NumberValue>;
private xAxisG: any;
private yAxisG: any;
private colorValue: (d: any) => any;
private xValue: (d: any) => any;
private yValue: (d: any) => any;
private idValue: (d: any) => any;
constructor(_config: ChartConfiguration, _data?: any[]) {
super(_config, _data);
this.initVis();
}
protected getDefaultMargins(): { top: number; right: number; bottom: number; left: number; } {
return { top: 25, right: 20, bottom: 20, left: 35 };
}
protected getDefaultContainerSize(): { width: number; height: number; } {
return { width: 600, height: 400 };
}
protected initVis(): void {
const vis = this;
vis.colorScale = d3.scaleOrdinal()
.range(['#d3eecd', '#7bc77e', '#2a8d46']) // light green to dark green
.domain(['Easy', 'Intermediate', 'Difficult']);
vis.xScale = d3.scaleLinear()
.range([0, vis.width]);
vis.yScale = d3.scaleLinear()
.range([vis.height, 0]);
// Initialize axes
vis.xAxis = d3.axisBottom(vis.xScale)
.ticks(6)
.tickSize(-vis.height - 10)
.tickPadding(10)
.tickFormat(d => d + ' km');
vis.yAxis = d3.axisLeft(vis.yScale)
.ticks(6)
.tickSize(-vis.width - 10)
.tickPadding(10);
// Append empty x-axis group and move it to the bottom of the chart
vis.xAxisG = vis.chart.append('g')
.attr('class', 'axis x-axis')
.attr('transform', `translate(0,${vis.height})`);
// Append y-axis group
vis.yAxisG = vis.chart.append('g')
.attr('class', 'axis y-axis');
// Append both axis titles
vis.chart.append('text')
.attr('class', 'axis-title')
.attr('y', vis.height - 15)
.attr('x', vis.width + 10)
.attr('dy', '.71em')
.style('text-anchor', 'end')
.text('Distance');
vis.svg.append('text')
.attr('class', 'axis-title')
.attr('x', 0)
.attr('y', 0)
.attr('dy', '.71em')
.text('Hours');
}
public updateVis(): void {
let vis = this;
// Specificy accessor functions
vis.colorValue = d => d.difficulty;
vis.xValue = d => d.time;
vis.yValue = d => d.distance;
// Set the scale input domains
vis.xScale.domain([0, d3.max(vis.data, vis.xValue)]);
vis.yScale.domain([0, d3.max(vis.data, vis.yValue)]);
vis.renderVis();
}
protected renderVis() {
let vis = this;
vis.idValue = (d: any) => d.trail;
// Bind data to circles
const circles = vis.chart.selectAll('.point')
.data(vis.data, (d: any) => vis.idValue(d));
// Enter phase
const circlesEnter = circles.enter()
.append('circle')
.attr('class', 'point')
.attr('r', 4)
.attr('fill', (d: any) => vis.colorScale(vis.colorValue(d)))
.attr('cy', vis.yScale(0)) // Start from y=0
.attr('cx', (d: any) => vis.xScale(vis.xValue(d)));
circlesEnter.transition()
.duration(1000)
.attr('cy', (d: any) => vis.yScale(vis.yValue(d))); // Animate to the final position
// Update phase
circlesEnter.merge(circles)
.transition()
.duration(1000)
.attr('cy', (d: any) => vis.yScale(vis.yValue(d)))
.attr('cx', (d: any) => vis.xScale(vis.xValue(d)))
.attr('fill', (d: any) => vis.colorScale(vis.colorValue(d)));
// Exit phase: remove circles that no longer have data
circles.exit()
.transition()
.duration(1000)
.attr('r', 0)
.remove();
// Update the axes/gridlines
vis.xAxisG
.call(vis.xAxis)
.call((g: any) => g.select('.domain').remove());
vis.yAxisG
.call(vis.yAxis)
.call((g: any) => g.select('.domain').remove());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment