Skip to content

Commit

Permalink
wip: thz data support in charts
Browse files Browse the repository at this point in the history
  • Loading branch information
puglet5 committed Nov 16, 2023
1 parent 1bcebfc commit bb91a65
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 42 deletions.
204 changes: 171 additions & 33 deletions app/javascript/controllers/chart_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ interface AxesSpec {
labels: string[]
reverse: boolean
y_min: number | null
type: string
cols_type: string
peak_label_precision: number
}

declare global {
Expand Down Expand Up @@ -110,9 +113,10 @@ export default class extends Controller {
showLabels = true
cubicInterpolationMode: string | undefined = undefined
displayLabelValues: number[][] = this.labelsValue?.map(e => e?.map(o => o.position).map(Number)) ?? []
reverseXAxis: boolean = this.axesSpecValue["reverse"]
reverseXAxis: boolean = this.axesSpecValue.reverse

connect() {
this.visualize()
if (this.compareValue) {
this.disableControls()
this.visualize()
Expand All @@ -125,50 +129,106 @@ export default class extends Controller {
)
}

parseCSV(rawData: string): Point[] {
parseCSV(rawData: string) {

const data: string[][] = Papa.parse(rawData).data
const transformNaN = (e: string) => { return e === "nan" ? NaN : e }

this.dataStepValue = Number(data[0][0]) - Number(data[1][0])
const config = {
header: this.axesSpecValue.type === "thz",
dynamicTyping: true,
skipEmptyLines: true,
transform: transformNaN
}

const xValue: number[] = data.map((d) => {
return parseFloat(Object.values(d)[0])
}).filter(value => !Number.isNaN(value))
const { data, errors, meta } = Papa.parse(rawData, config)

Check failure on line 143 in app/javascript/controllers/chart_controller.ts

View workflow job for this annotation

GitHub Actions / Linters

'errors' is assigned a value but never used

const yValue: number[] = data.map((d) => {
return parseFloat(Object.values(d)[1])
}).filter(value => !Number.isNaN(value))
let header = meta.fields ?? ["x", "y"]

Check failure on line 145 in app/javascript/controllers/chart_controller.ts

View workflow job for this annotation

GitHub Actions / Linters

'header' is never reassigned. Use 'const' instead

const parsedData: Point[] = xValue
.map((v, i) => {
return [v, yValue[i]]
})
.map((v) => {
const [x, y] = v
return { x, y }
let rows = data.map(e => Object.values(e))

Check failure on line 147 in app/javascript/controllers/chart_controller.ts

View workflow job for this annotation

GitHub Actions / Linters

'rows' is never reassigned. Use 'const' instead
let cols = rows[0].map((_, colIndex) => rows.map(row => row[colIndex]))

Check failure on line 148 in app/javascript/controllers/chart_controller.ts

View workflow job for this annotation

GitHub Actions / Linters

'cols' is never reassigned. Use 'const' instead

return this.toChartData(cols, header, this.axesSpecValue.cols_type)
}

toChartData(data: number[][], fields: string[], axesSpec: string) {
// [[x[], y[], y[]], [x[], y[]]]
let parsedData: number[][][] = []

Check failure on line 155 in app/javascript/controllers/chart_controller.ts

View workflow job for this annotation

GitHub Actions / Linters

'parsedData' is never reassigned. Use 'const' instead
let latestXIndex: number = -1
axesSpec.split('').forEach((e, i) => {

Check warning on line 157 in app/javascript/controllers/chart_controller.ts

View workflow job for this annotation

GitHub Actions / Linters

Strings must use doublequote
if (e === "x") {
latestXIndex += 1
parsedData.push([data[i]])
} else if (e === "y") {
parsedData[latestXIndex].push(data[i])
}
})

// [[x[], y[]], ...]

let convertedDataIntermediate = parsedData.map((traceE, traceI) => traceE.map((e, i) => {

Check failure on line 168 in app/javascript/controllers/chart_controller.ts

View workflow job for this annotation

GitHub Actions / Linters

'convertedDataIntermediate' is never reassigned. Use 'const' instead

Check failure on line 168 in app/javascript/controllers/chart_controller.ts

View workflow job for this annotation

GitHub Actions / Linters

'traceI' is defined but never used
return [traceE[0], traceE[i]]
}).slice(1))

let dataDimensions = convertedDataIntermediate.map(e => e.length)

Check failure on line 172 in app/javascript/controllers/chart_controller.ts

View workflow job for this annotation

GitHub Actions / Linters

'dataDimensions' is never reassigned. Use 'const' instead
let convertedData = convertedDataIntermediate.flat()

Check failure on line 173 in app/javascript/controllers/chart_controller.ts

View workflow job for this annotation

GitHub Actions / Linters

'convertedData' is never reassigned. Use 'const' instead

console.log(convertedDataIntermediate)

let objectData: Point[][] = convertedData.map((data, i) => {

Check failure on line 177 in app/javascript/controllers/chart_controller.ts

View workflow job for this annotation

GitHub Actions / Linters

'objectData' is never reassigned. Use 'const' instead
return data[0].map((v, i) => {
return [v, data[1][i]]
})
.map((v) => {
const [x, y] = v
return { x, y }
})
})

return { data: objectData, dimensions: dataDimensions }
}

constructDatasets(data: Point[][], dimensions: number[], labels: string[]) {
let traceNum = 1
return labels.map((label, i) => {
if (i === dimensions[0]) {
traceNum += 1
}
console.log(traceNum)
return {
data: data[i],
label: label,
showLine: true,
lineTension: 0,
yAxisID: `y${traceNum}`,
xAxisID: `x${traceNum}`
}
})
}

constructScales(dimenisons: number[], labels: string[]) {

return parsedData
}

async visualize() {

if (!this.hasCurrentPlotDataValue && !this.hasUnmodifiedPlotDataValue) {
const raw = await this.import(this.urlsValue)
this.unmodifiedPlotDataValue = raw.map(r => this.parseCSV(r))
}
// if (!this.hasCurrentPlotDataValue && !this.hasUnmodifiedPlotDataValue) {
// const raw = await this.import(this.urlsValue)
// this.unmodifiedPlotDataValue = raw.map(r => this.parseCSV(r))
// }

if (this.hasUnmodifiedPlotDataValue && !this.hasCurrentPlotDataValue) {
this.currentPlotDataValue = this.unmodifiedPlotDataValue
}
// if (this.hasUnmodifiedPlotDataValue && !this.hasCurrentPlotDataValue) {
// this.currentPlotDataValue = this.unmodifiedPlotDataValue
// }

if (window.scatterChart) { window.scatterChart.destroy() }
let rawData = await this.import(this.urlsValue)
let parsedData = rawData.map(r => this.parseCSV(r)).flat()[0]
let datasets = this.constructDatasets(parsedData.data, parsedData.dimensions, ["Refraction", "Absorption", "Signal"])

const id = this.canvasIdValue
if (window.scatterChart) { window.scatterChart.destroy() }

const makeChart = (data, filenames) => {

window.scatterChart = new Chart(`canvas-${id}`, {
window.scatterChart = new Chart(`canvas-${this.canvasIdValue}`, {
type: "scatter",
data: {
datasets: data.map((d, i) => ({
Expand Down Expand Up @@ -197,7 +257,7 @@ export default class extends Controller {
return false
},
formatter: (value) => {
return parseFloat(value["x"]).toFixed(this.axesSpecValue["peak_label_precision"])
return parseFloat(value["x"]).toFixed(this.axesSpecValue.peak_label_precision)
}
},
})),
Expand Down Expand Up @@ -232,10 +292,10 @@ export default class extends Controller {
y: {
border: { dash: [4, 4] },
title: {
text: this.transmissionPlot ? "Transmission, %" : this.axesSpecValue["labels"][1],
text: this.transmissionPlot ? "Transmission, %" : this.axesSpecValue.labels[1],
display: true
},
min: this.axesSpecValue["y_min"],
min: this.axesSpecValue.y_min,
grid: {
color: this.darkValue ? "#1e1e1e" : "#e1e1e1",
tickBorderDash: [0, 0],
Expand All @@ -247,7 +307,7 @@ export default class extends Controller {
x: {
border: { dash: [4, 4] },
title: {
text: this.axesSpecValue["labels"][0],
text: this.axesSpecValue.labels[0],
display: true
},
grid: {
Expand Down Expand Up @@ -343,7 +403,85 @@ export default class extends Controller {
})
}

makeChart(this.currentPlotDataValue, this.filenamesValue)
window.scatterChart = new Chart(`canvas-${this.canvasIdValue}`, {
type: "scatter",
data: {
datasets: datasets
},
options: {
events: ["dblclick", 'mousemove', 'mouseout', 'click', "drag", "wheel"],

Check warning on line 412 in app/javascript/controllers/chart_controller.ts

View workflow job for this annotation

GitHub Actions / Linters

Strings must use doublequote
locale: "fr",
animation: false,
responsive: true,
layout: {
padding: {
left: 20,
right: 20,
top: 20,
bottom: 20
},
},
elements: {
point: {
radius: 0
},
line: this.compareValue ? {
borderWidth: 2
} :
{
backgroundColor: this.darkValue ? "#ffffff" : "#000000",
borderColor: this.darkValue ? "#ffffff" : "#000000",
borderWidth: 2
}
},
scales: {
y: { display: false },
x: { display: false },

y1: {
display: 'auto',
type: 'linear',
position: 'left',
},
y2: {
display: 'auto',
type: 'linear',
position: 'right',
grid: {
drawOnChartArea: false,
},
},
x1: {
display: 'auto',
position: "bottom",
},
x2: {
display: 'auto',
position: "top",
grid: {
drawOnChartArea: false,
},
}
},
interaction: {
mode: "nearest",
axis: "x",
intersect: false
},
plugins: {
legend:
{
labels: {
boxWidth: 0,
}
},
datalabels: {
display: false
}
}
},
})

}

getRange(data: Point[]): number[][] {
Expand Down
18 changes: 9 additions & 9 deletions app/models/spectrum.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ class Spectrum < RsdbRecord
include ParseMetadata

AXES_SPEC = {
not_set: { labels: ['', ''], reverse: false, peak_label_precision: 0, y_min: 0 },
xrf: { labels: ['Energy, keV', 'Intensity, a.u.'], reverse: false, peak_label_precision: 2, y_min: 0 },
xrd: { labels: ['2Theta, degrees', 'Intensity, a.u.'], reverse: false, peak_label_precision: 2, y_min: 0 },
ftir: { labels: ['Wavenumber, 1/cm', 'Intensity, a.u.'], reverse: true, peak_label_precision: 0, y_min: 0 },
libs: { labels: ['Wavelength, nm', 'Intensity, a.u.'], reverse: false, peak_label_precision: 0, y_min: 0 },
raman: { labels: ['Raman shift, 1/cm', 'Intensity, a.u.'], reverse: false, peak_label_precision: 0, y_min: 0 },
thz: { labels: ['Frequency, THz', 'Intensity, a.u.'], reverse: false, peak_label_precision: 2, y_min: nil },
reflectance: { labels: ['Wavelength, nm', 'Reflectance, %'], reverse: false, peak_label_precision: 0, y_min: 0 },
other: { labels: ['', ''], reverse: false }
not_set: { cols_type: 'xy', type: 'not_set', labels: ['', ''], reverse: false, peak_label_precision: 0, y_min: 0 },
xrf: { cols_type: 'xy', type: 'xrf', labels: ['Energy, keV', 'Intensity, a.u.'], reverse: false, peak_label_precision: 2, y_min: 0 },
xrd: { cols_type: 'xy', type: 'xrd', labels: ['2Theta, degrees', 'Intensity, a.u.'], reverse: false, peak_label_precision: 2, y_min: 0 },
ftir: { cols_type: 'xy', type: 'ftir', labels: ['Wavenumber, 1/cm', 'Intensity, a.u.'], reverse: true, peak_label_precision: 0, y_min: 0 },
libs: { cols_type: 'xy', type: 'libs', labels: ['Wavelength, nm', 'Intensity, a.u.'], reverse: false, peak_label_precision: 0, y_min: 0 },
raman: { cols_type: 'xy', type: 'raman', labels: ['Raman shift, 1/cm', 'Intensity, a.u.'], reverse: false, peak_label_precision: 0, y_min: 0 },
thz: { cols_type: 'xyyxy', type: 'thz', labels: ['Frequency, THz', 'Intensity, a.u.'], reverse: false, peak_label_precision: 2, y_min: nil },
reflectance: { cols_type: 'xy', type: 'reflectance', labels: ['Wavelength, nm', 'Reflectance, %'], reverse: false, peak_label_precision: 0, y_min: 0 },
other: { cols_type: 'xy', type: 'other', labels: ['', ''], reverse: false }
}.freeze

HEADER_MATCH_TABLE = {
Expand Down

0 comments on commit bb91a65

Please sign in to comment.