Commit 690d9cd5 authored by François Agneray's avatar François Agneray

SpectraGraph d3 v0

parent 2bcff621
svg {
background-color: #F5F5F5;
}
.titles text {
fill: #5a5a5a;
font-family: "sans-serif";
......@@ -37,28 +33,28 @@ svg {
}
.emission line {
stroke: blue;
stroke: steelblue;
}
.emission text {
fill: blue;
fill: steelblue;
}
.absorption line {
stroke: red;
stroke: tomato;
}
.absorption text {
fill: red;
fill: tomato;
}
.line {
.spectra-line, .line {
fill: none;
stroke: black;
stroke-width: 1px;
}
.area {
.spectra-area, .area {
fill: #D0D0D0;
opacity: 0.6;
}
......@@ -69,7 +65,7 @@ svg {
}
.big-circle-tootlip {
fill: #CCE5F6;
fill: #7ac29a;
}
.little-circle-tooltip {
......@@ -80,7 +76,7 @@ svg {
.rect-tootlip {
fill: #fafafa;
stroke: #3498db;
stroke: #7ac29a;
opacity: 0.9;
stroke-width: 1;
}
......@@ -94,4 +90,35 @@ svg {
.text-y-value {
font-weight: bold;
}
.emission-button circle {
stroke: steelblue;
stroke-width: 3px;
cursor: pointer;
}
.button-off circle {
fill: transparent;
}
.emission-button-on circle {
fill: steelblue;
}
.absorption-button circle {
stroke: tomato;
stroke-width: 3px;
cursor: pointer;
}
.absorption-button-on circle {
fill: tomato;
}
.emission-button text, .absorption-button text {
font-size: 13px;
font-family: "sans-serif";
color: #333333;
fill: #333333;
}
\ No newline at end of file
......@@ -3,6 +3,7 @@ import { Component, ChangeDetectionStrategy, OnInit, ViewEncapsulation } from '@
import * as d3 from 'd3';
import { emissionLines, absorptionLines } from './rays';
import { BaseType } from 'd3';
type SpectraType = "x" | "Flux";
interface Point {
......@@ -19,13 +20,20 @@ interface Point {
})
export class GraphComponent implements OnInit {
z = 0.2096;
svg: d3.Selection<SVGGElement, unknown, HTMLElement, any>;
svg: d3.Selection<BaseType, unknown, HTMLElement, any>;
focus: d3.Selection<SVGGElement, unknown, HTMLElement, any>;
width: number;
height: number;
brushHeight: number;
margin = { top: 10, right: 30, bottom: 150, left: 100 };
margin = { top: 50, right: 30, bottom: 150, left: 100 };
x: d3.ScaleLinear<number, number>;
xAxis: d3.Axis<number | { valueOf(): number }>;
y: d3.ScaleLinear<number, number>;
spectraLine: d3.Line<Point>;
spectraArea: d3.Area<Point>;
displayEmissionLines = true;
dispayAbsorptionLines = true;
ngOnInit() {
const url = "https://cesam.lam.fr/anis-tools/spectra_to_csv/?file=/dataproject/SPECTRA/VIPERS_DR2/spec1D/VIPERS_101121877_bis.fits";
......@@ -46,6 +54,7 @@ export class GraphComponent implements OnInit {
this.addSpectraArea(dataset);
this.addRays();
this.addAxis();
this.addRaysButtons();
this.addBrush(dataset);
this.addTooltip(this.width, this.height, dataset, this.x, this.y);
});
......@@ -54,13 +63,21 @@ export class GraphComponent implements OnInit {
private initSvg(): void {
this.svg = d3.select("svg#mygraph")
.attr("width", this.width + this.margin.left + this.margin.right)
.attr("height", this.height + this.margin.top + this.margin.bottom)
.append("g")
.attr("height", this.height + this.margin.top + this.margin.bottom);
this.svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", this.width)
.attr("height", this.height);
this.focus = this.svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")");
}
private addTitles(): void {
const titles = this.svg.append("g")
const titles = this.focus.append("g")
.attr("class", "titles");
titles.append("text")
......@@ -88,24 +105,28 @@ export class GraphComponent implements OnInit {
}
private addGrid(): void {
const grid = this.svg.append("g")
const grid = this.focus.append("g")
.attr("class", "grid");
grid.append("g")
.attr("class", "grid-x")
.selectAll()
.data(this.x.ticks(10))
.enter()
.append("line")
.attr("class", "grid-line-x")
.attr("x1", (d: number) => this.x(d))
.attr("x2", (d: number) => this.x(d))
.attr("y1", 0)
.attr("y2", this.height);
grid.append("g")
.attr("class", "grid-y")
.selectAll()
.data(this.y.ticks(10))
.enter()
.append("line")
.attr("class", "grid-line-y")
.attr("x1", 0)
.attr("x2", this.width)
.attr("y1", (d: number) => this.y(d))
......@@ -113,46 +134,42 @@ export class GraphComponent implements OnInit {
}
private addSpectraLine(dataset: d3.DSVParsedArray<Point>): void {
this.svg.append("g")
.attr("class", "line")
.append("path")
.datum(dataset)
.attr("d", this.getLine());
}
private getLine(): d3.Line<Point> {
return d3.line<Point>()
this.spectraLine = d3.line<Point>()
.x((d) => this.x(d.x))
.y((d) => this.y(d.y));
}
private addSpectraArea(dataset: d3.DSVParsedArray<Point>): void {
this.svg.append("g")
.attr("class", "area")
this.focus.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(dataset)
.attr("d", this.getArea());
.attr("class", "spectra-line")
.attr("d", this.spectraLine);
}
private getArea(): d3.Area<Point> {
return d3.area<Point>()
private addSpectraArea(dataset: d3.DSVParsedArray<Point>): void {
this.spectraArea = d3.area<Point>()
.x((d) => this.x(d.x))
.y0(this.height)
.y1((d) => this.y(d.y));
this.focus.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(dataset)
.attr("class", "spectra-area")
.attr("d", this.spectraArea);
}
private addRays(): void {
const coef = 1 + this.z;
const wavelengthFilter = r => this.x(r.wavelength * coef) > 0 && this.x(r.wavelength * coef) < this.width;
const filteredEmissionLines = emissionLines.filter(wavelengthFilter);
const filteredAbsorptionLines = absorptionLines.filter(wavelengthFilter);
const er = this.svg.append("g")
const er = this.focus.append("g")
.style("display", "block")
.attr("class", "ray emission")
.attr("clip-path", "url(#clip)");
er.selectAll()
.data(filteredEmissionLines)
.data(emissionLines)
.enter()
.append("line")
.attr("x1", r => this.x(r.wavelength * coef))
......@@ -161,7 +178,7 @@ export class GraphComponent implements OnInit {
.attr("y2", 0);
er.selectAll()
.data(filteredEmissionLines)
.data(emissionLines)
.enter()
.append("text")
.attr("x", r => this.x(r.wavelength * coef) - 5)
......@@ -169,11 +186,13 @@ export class GraphComponent implements OnInit {
.attr("transform", r => "rotate(-90, " + (this.x(r.wavelength * coef) - 5) + "," + this.height * 0.2 + ")")
.text(r => r.name);
const ar = this.svg.append("g")
const ar = this.focus.append("g")
.style("display", "block")
.attr("class", "ray absorption")
.attr("clip-path", "url(#clip)");
ar.selectAll()
.data(filteredAbsorptionLines)
.data(absorptionLines)
.enter()
.append("line")
.attr("x1", r => this.x(r.wavelength * coef))
......@@ -182,7 +201,7 @@ export class GraphComponent implements OnInit {
.attr("y2", 0);
ar.selectAll()
.data(filteredAbsorptionLines)
.data(absorptionLines)
.enter()
.append("text")
.attr("x", r => this.x(r.wavelength * coef) - 5)
......@@ -192,16 +211,74 @@ export class GraphComponent implements OnInit {
}
private addAxis(): void {
this.svg.append("g")
.attr("class", "axis")
this.xAxis = d3.axisBottom(this.x);
this.focus.append("g")
.attr("class", "axis axis-x")
.attr("transform", "translate(0," + this.height + ")")
.call(d3.axisBottom(this.x));
.call(this.xAxis);
this.svg.append("g")
.attr("class", "axis")
this.focus.append("g")
.attr("class", "axis axis-y")
.call(d3.axisLeft(this.y).tickFormat(d3.format(".1e")))
}
private addRaysButtons(): void {
const gButtons = this.svg.append("g")
.attr("class", "rays-buttons")
.attr("transform", "translate(" + this.margin.left + "," + 25 + ")");
const emission = gButtons.append("g")
.attr("class", "emission-button emission-button-on");
const circleEmission = emission.append("circle")
.attr("cx", 20)
.attr("cy", 0)
.attr("r", 10);
circleEmission.on("click", () => {
const e = this.focus.select(".emission");
if (this.displayEmissionLines) {
e.style("display", "none");
emission.attr("class", "emission-button button-off");
} else {
e.style("display", "block");
emission.attr("class", "emission-button emission-button-on");
}
this.displayEmissionLines = !this.displayEmissionLines;
});
emission.append("text")
.attr("x", 35)
.attr("y", 5)
.text("Display emission lines")
const absorption = gButtons.append("g")
.attr("class", "absorption-button absorption-button-on");
const circleAbsorption = absorption.append("circle")
.attr("cx", 215)
.attr("cy", 0)
.attr("r", 10);
circleAbsorption.on("click", () => {
const a = this.focus.select(".absorption");
if (this.dispayAbsorptionLines) {
a.style("display", "none");
absorption.attr("class", "absorption-button button-off");
} else {
a.style("display", "block");
absorption.attr("class", "absorption-button absorption-button-on");
}
this.dispayAbsorptionLines = !this.dispayAbsorptionLines;
});
absorption.append("text")
.attr("x", 230)
.attr("y", 5)
.text("Display absorption lines")
}
private addTooltip(
width: number,
height: number,
......@@ -209,7 +286,7 @@ export class GraphComponent implements OnInit {
x: d3.ScaleLinear<number, number>,
y: d3.ScaleLinear<number, number>
): void {
const tooltip = this.svg.append("g")
const tooltip = this.focus.append("g")
.style("display", "none");
tooltip.append("circle")
......@@ -241,7 +318,7 @@ export class GraphComponent implements OnInit {
.attr("class", "text-y-value")
const bisectX = d3.bisector((p: Point) => p.x).left;
this.svg.append("rect")
this.focus.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
......@@ -260,9 +337,9 @@ export class GraphComponent implements OnInit {
}
private addBrush(dataset: d3.DSVParsedArray<Point>): void {
const brush = this.svg.append("g")
.attr("class", "brush")
.attr("transform", "translate(" + 0 + "," + 470 + ")");
const context = this.svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + this.margin.left + "," + 480 + ")");
const xBrush = d3.scaleLinear().range([0, this.width]);
const yBrush = d3.scaleLinear().range([this.brushHeight, 0]);
......@@ -271,44 +348,89 @@ export class GraphComponent implements OnInit {
const xBrushAxis = d3.axisBottom(xBrush);
brush.append("g")
const lineBrush = d3.line<Point>()
.x((d) => xBrush(d.x))
.y((d) => yBrush(d.y));
context.append("g")
.attr("class", "line")
.append("path")
.datum(dataset)
.attr("d", this.getLineBrush(xBrush, yBrush));
.attr("d", lineBrush);
const areaBrush = d3.area<Point>()
.x((d) => xBrush(d.x))
.y0(this.brushHeight)
.y1((d) => yBrush(d.y));
brush.append("g")
context.append("g")
.attr("class", "area")
.append("path")
.datum(dataset)
.attr("d", this.getAreaBrush(xBrush, yBrush));
.attr("d", areaBrush);
brush.append("g")
context.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + this.brushHeight + ")")
.call(xBrushAxis);
/* const brush = d3.brushX()
.extent([[0, 0], [this.width, this.height2]])
.on("brush end", brushed);
const zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([[0, 0], [this.width, this.height]])
.extent([[0, 0], [this.width, this.height]])
.on("zoom", zoomed); */
}
private getLineBrush(xBrush: d3.ScaleLinear<number, number>, yBrush: d3.ScaleLinear<number, number>): d3.Line<Point> {
return d3.line<Point>()
.x((d) => xBrush(d.x))
.y((d) => yBrush(d.y));
}
const brush = d3.brushX()
.extent([[0, 0], [this.width, this.brushHeight]])
.on("end", () => {
const selection = d3.event.selection || xBrush.range();
this.x.domain(selection.map(xBrush.invert, xBrush));
// Update spectra graph
this.focus.select(".spectra-line")
.attr("d", this.spectraLine);
this.focus.select(".spectra-area")
.attr("d", this.spectraArea);
// Update axis
this.focus.select(".axis-x").call(this.xAxis);
// Update grid
this.focus.selectAll(".grid-line-x")
.remove();
this.focus.select(".grid-x")
.selectAll()
.data(this.x.ticks())
.enter()
.append("line")
.attr("class", "grid-line-x")
.attr("x1", (d: number) => this.x(d))
.attr("x2", (d: number) => this.x(d))
.attr("y1", 0)
.attr("y2", this.height);
// Update rays
const coef = 1 + this.z;
this.focus.select(".emission")
.selectAll("line")
.data(emissionLines)
.attr("x1", r => this.x(r.wavelength * coef))
.attr("x2", r => this.x(r.wavelength * coef));
this.focus.select(".emission")
.selectAll("text")
.data(emissionLines)
.attr("x", r => this.x(r.wavelength * coef) - 5)
.attr("transform", r => "rotate(-90, " + (this.x(r.wavelength * coef) - 5) + "," + this.height * 0.2 + ")")
this.focus.select(".absorption")
.selectAll("line")
.data(absorptionLines)
.attr("x1", r => this.x(r.wavelength * coef))
.attr("x2", r => this.x(r.wavelength * coef));
this.focus.select(".absorption")
.selectAll("text")
.data(absorptionLines)
.attr("x", r => this.x(r.wavelength * coef) - 5)
.attr("transform", r => "rotate(-90, " + (this.x(r.wavelength * coef) - 5) + "," + this.height * 0.8 + ")")
});
private getAreaBrush(xBrush: d3.ScaleLinear<number, number>, yBrush: d3.ScaleLinear<number, number>): d3.Area<Point> {
return d3.area<Point>()
.x((d) => xBrush(d.x))
.y0(this.brushHeight)
.y1((d) => yBrush(d.y));
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, this.x.range());
}
}
\ No newline at end of file
}
......@@ -8,4 +8,6 @@
</table>
</div>
<app-graph></app-graph>
\ No newline at end of file
<div *ngIf="spec1DExists()">
<app-graph></app-graph>
</div>
\ No newline at end of file
......@@ -16,4 +16,8 @@ export class ObjectDisplayComponent {
.filter(a => a.detail)
.sort((a, b) => a.display_detail - b.display_detail);
}
spec1DExists(): boolean {
return this.attributeList.filter(a => a.renderer_detail === 'spectra').length > 0;
}
}
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