File

services/spectra-plots.service.ts

Index

Methods

Constructor

constructor(mathService: MathService)
Parameters :
Name Type Optional
mathService MathService No

Methods

Private createSpectraComponentsPlot
createSpectraComponentsPlot(serviceResponses: ServiceResponses, availableModels: Parameter[], form: ControlForm, plot: NshmpPlot)

Create the response spectra components plot.

Parameters :
Name Type Optional Description
serviceResponses ServiceResponses No

The service reponses

availableModels Parameter[] No

The avaialable models

form ControlForm No

The control panel form values

plot NshmpPlot No

The current spectra curves plot

Returns : PlotlyPlot
Private createSpectraCurvesPlot
createSpectraCurvesPlot(serviceResponses: ServiceResponses, availableModels: Parameter[], form: ControlForm, plot: NshmpPlot)

Create the response spectra curves plot.

Parameters :
Name Type Optional Description
serviceResponses ServiceResponses No

The service reponses

availableModels Parameter[] No

The avaialable models

form ControlForm No

The control panel form values

plot NshmpPlot No

The current spectra curves plot

Returns : PlotlyPlot
Private createSpectraDiffPlot
createSpectraDiffPlot(serviceResponses: ServiceResponses, availableModels: Parameter[], form: ControlForm, plot: NshmpPlot)

Create the spectra difference plot.

Parameters :
Name Type Optional Description
serviceResponses ServiceResponses No

The service responses

availableModels Parameter[] No

The available models

form ControlForm No

The control form values

plot NshmpPlot No

The current spectra diffecence plot

Returns : PlotlyPlot
Private createSpectraDiffPlotData
createSpectraDiffPlotData(serviceResponses: ServiceResponses, availableModels: Parameter[], form: ControlForm)

Create the spectra difference plot data.

Parameters :
Name Type Optional Description
serviceResponses ServiceResponses No

The service responses

availableModels Parameter[] No

The available models

form ControlForm No

The control form values

Returns : Partial[]
Private createSpectraPlot
createSpectraPlot(options: CreateSpectraPlotOptions)

Create a response spectra plot with given options.

Parameters :
Name Type Optional Description
options CreateSpectraPlotOptions No

The create plot options

Returns : PlotlyPlot
Private createSpectraPlotData
createSpectraPlotData(options: CreateSpectraPlotDataOptions)

Create the PlotData for a spectra plot.

Creates plot data for response spectra for current return period.

Parameters :
Name Type Optional Description
options CreateSpectraPlotDataOptions No

The plot data options

Returns : Partial[]
createSpectraPlots
createSpectraPlots(state: AppState, formGroup: FormGroupControls<ControlForm>)

Create the spectra plots:

  • Response spectra for total source type
  • Percent difference for total source type
  • Response spectra components
Parameters :
Name Type Optional Description
state AppState No

The app state

formGroup FormGroupControls<ControlForm> No
Returns : Map<string, NshmpPlot>
spectraPercentDifference
spectraPercentDifference(serviceResponses: ServiceResponses, returnPeriod: number)
Parameters :
Name Type Optional
serviceResponses ServiceResponses No
returnPeriod number No
Returns : SpectraDifference
Private spectraPgaLegendEntry
spectraPgaLegendEntry(name: string, pgaMarker: string)

Create the spectra PGA legend entry.

Parameters :
Name Type Optional Default value Description
name string No

The legend entry name

pgaMarker string No 'square'

The PGA marker symbol

Returns : Partial<PlotData>
import {Injectable} from '@angular/core';
import {FormGroupControls} from '@ghsc/nshmp-lib-ng/nshmp';
import {NshmpPlot, plotUtils} from '@ghsc/nshmp-lib-ng/plot';
import {Imt, imtToPeriod} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/gmm';
import {
  SourceType,
  sourceTypeToCapitalCase,
} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/model';
import {Parameter} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata';
import {PlotlyPlot} from '@ghsc/nshmp-utils-ts/libs/plotly';
import {Dash, PlotData} from 'plotly.js';

import {
  AppState,
  ControlForm,
  Plots,
  ServiceResponse,
  ServiceResponses,
  Spectra,
} from '../models/state.model';
import {MathService} from './math.service';

export interface SpectraDifference {
  imts: Imt[];
  percentDifference: number[];
}

/**
 * Base options for creating spectra plots.
 */
interface BaseCreateSpectraPlotOptions {
  /** Current avialable models */
  availableModels: Parameter[];
  /** Control panel form values */
  form: ControlForm;
  /** Function to return the name of the data */
  name: (modelInfo: Parameter, spectra: Spectra) => string;
  /** Function to filter the response spectra data */
  spectraFilter: (spectra: Spectra) => boolean;
}

/**
 * Options for creating spectra plot data.
 */
interface CreateSpectraPlotDataOptions extends BaseCreateSpectraPlotOptions {
  /** The service response for a model */
  serviceResponse: ServiceResponse;

  /** The line colors to use for the spectra data */
  colors?: string[];
  /** The dash type */
  dash?: Dash;
  /** The PGA marker symbol */
  pgaMarker?: string;
}

/**
 * Options to create a spectra plot.
 */
interface CreateSpectraPlotOptions extends BaseCreateSpectraPlotOptions {
  /** Plot id */
  id: Plots;
  /** Current plot */
  plot: NshmpPlot;
  /** The service resoponses */
  serviceResponses: ServiceResponses;

  /** The line colors to use for each model */
  colors?: PlotColors;
  /** Dash types per model */
  dash?: PlotDash;
  /** The PGA marker symbol per model */
  pgaMarker?: PgaMarker;
}

/**
 * Dash type for models
 */
interface PlotDash {
  /** Dash type for model A */
  modelA?: Dash;
  /** Dash type for model B */
  modelB?: Dash;
}

/**
 * PGA marker symbol per model.
 */
interface PgaMarker {
  /** Model A PGA symbol */
  modelA?: string;
  /** Model B PGA symbol */
  modelB?: string;
}

/**
 * Line plot colors per model.
 */
interface PlotColors {
  /** Model A line colors */
  modelA?: string[];
  /** Model B line colors */
  modelB?: string[];
}

@Injectable({
  providedIn: 'root',
})
export class SpectraPlotsService {
  constructor(private mathService: MathService) {}

  /**
   * Create the spectra plots:
   *  - Response spectra for total source type
   *  - Percent difference for total source type
   *  - Response spectra components
   *
   * @param state The app state
   */
  createSpectraPlots(
    state: AppState,
    formGroup: FormGroupControls<ControlForm>,
  ): Map<string, NshmpPlot> {
    const plots = new Map<string, NshmpPlot>();

    const spectraPlot = state.plots.get(Plots.SPECTRUM);
    const spectraComponentsPlot = state.plots.get(Plots.SPECTRUM_COMPONENTS);
    const spectraDiffPlot = state.plots.get(Plots.SPECTRUM_DIFFERENCES);

    const spectraCurves = this.createSpectraCurvesPlot(
      state.serviceResponses,
      state.availableModels,
      formGroup.getRawValue(),
      spectraPlot,
    );

    plots.set(Plots.SPECTRUM, {
      ...spectraPlot,
      plotData: spectraCurves,
    });

    const spectraComponents = this.createSpectraComponentsPlot(
      state.serviceResponses,
      state.availableModels,
      formGroup.getRawValue(),
      spectraPlot,
    );

    plots.set(Plots.SPECTRUM_COMPONENTS, {
      ...spectraComponentsPlot,
      plotData: spectraComponents,
    });

    const spectraDiff = this.createSpectraDiffPlot(
      state.serviceResponses,
      state.availableModels,
      formGroup.getRawValue(),
      spectraDiffPlot,
    );

    plots.set(Plots.SPECTRUM_DIFFERENCES, {
      ...spectraDiffPlot,
      plotData: spectraDiff,
    });

    return plots;
  }

  spectraPercentDifference(
    serviceResponses: ServiceResponses,
    returnPeriod: number,
  ): SpectraDifference {
    const {modelA, modelB} = serviceResponses;

    const spectraA = modelA.spectra.find(
      spectra => spectra.sourceType === SourceType.TOTAL,
    );
    const spectraB = modelB.spectra.find(
      spectra => spectra.sourceType === SourceType.TOTAL,
    );

    const xValues = spectraA.responseSpectra.imts.filter(imt =>
      spectraB.responseSpectra.imts.includes(imt),
    );

    const returnPeriodSpectraA = spectraA.responseSpectra.responseSpectrum.find(
      spectra => spectra.returnPeriod === returnPeriod,
    );
    const returnPeriodSpectraB = spectraB.responseSpectra.responseSpectrum.find(
      spectra => spectra.returnPeriod === returnPeriod,
    );

    const yValuesA = spectraA.responseSpectra.imts
      .filter(imt => xValues.includes(imt))
      .map((_, i) => returnPeriodSpectraA.values[i]);
    const yValuesB = spectraB.responseSpectra.imts
      .filter(imt => xValues.includes(imt))
      .map((_, i) => returnPeriodSpectraB.values[i]);

    const yValues = this.mathService.percentDifferences(yValuesA, yValuesB);

    return {
      imts: xValues,
      percentDifference: yValues,
    };
  }

  /**
   * Create the response spectra curves plot.
   *
   * @param serviceResponses The service reponses
   * @param availableModels The avaialable models
   * @param form The control panel form values
   * @param plot The current spectra curves plot
   */
  private createSpectraCurvesPlot(
    serviceResponses: ServiceResponses,
    availableModels: Parameter[],
    form: ControlForm,
    plot: NshmpPlot,
  ): PlotlyPlot {
    const spectraFilter = (spectra: Spectra) =>
      spectra.sourceType === SourceType.TOTAL;
    const name = (modelInfo: Parameter) => modelInfo.display;
    const colors = plotUtils.COLORWAY;

    return this.createSpectraPlot({
      availableModels,
      colors: {
        modelA: [colors[0]],
        modelB: [colors[1]],
      },
      form,
      id: Plots.SPECTRUM,
      name,
      plot,
      serviceResponses,
      spectraFilter,
    });
  }

  /**
   * Create the response spectra components plot.
   *
   * @param serviceResponses The service reponses
   * @param availableModels The avaialable models
   * @param form The control panel form values
   * @param plot The current spectra curves plot
   */
  private createSpectraComponentsPlot(
    serviceResponses: ServiceResponses,
    availableModels: Parameter[],
    form: ControlForm,
    plot: NshmpPlot,
  ): PlotlyPlot {
    const spectraFilter = (spectra: Spectra) =>
      spectra.sourceType !== SourceType.TOTAL;
    const name = (modelInfo: Parameter, spectra: Spectra) =>
      `${modelInfo.display} - ${sourceTypeToCapitalCase(spectra.sourceType)}`;

    return this.createSpectraPlot({
      availableModels,
      dash: {
        modelA: 'solid',
        modelB: 'dash',
      },
      form,
      id: Plots.SPECTRUM_COMPONENTS,
      name,
      pgaMarker: {
        modelA: 'square',
        modelB: 'diamond',
      },
      plot,
      serviceResponses,
      spectraFilter,
    });
  }

  /**
   * Create the spectra difference plot.
   *
   * @param serviceResponses The service responses
   * @param availableModels The available models
   * @param form The control form values
   * @param plot The current spectra diffecence plot
   */
  private createSpectraDiffPlot(
    serviceResponses: ServiceResponses,
    availableModels: Parameter[],
    form: ControlForm,
    plot: NshmpPlot,
  ): PlotlyPlot {
    const data = this.createSpectraDiffPlotData(
      serviceResponses,
      availableModels,
      form,
    );

    const yValues = data
      .map(d => d.y as number[])
      .reduce((previous, current) => [...previous, ...current]);

    const yMax = Math.max(...yValues.map(y => Math.abs(y))) * 1.3;
    const yRange = [-yMax, yMax];

    const {layout, mobileLayout} = plot.plotData;

    return {
      config: {...plot.plotData.config},
      data,
      id: Plots.SPECTRUM_DIFFERENCES,
      layout: {
        ...layout,
        yaxis: {
          ...layout.yaxis,
          autorange: false,
          range: yRange,
        },
      },
      mobileConfig: {...plot.plotData.mobileConfig},
      mobileLayout: {
        ...mobileLayout,
        yaxis: {
          ...mobileLayout.yaxis,
          autorange: false,
          range: yRange,
        },
      },
    };
  }

  /**
   * Create the spectra difference plot data.
   *
   * @param serviceResponses The service responses
   * @param availableModels The available models
   * @param form The control form values
   */
  private createSpectraDiffPlotData(
    serviceResponses: ServiceResponses,
    availableModels: Parameter[],
    form: ControlForm,
  ): Partial<PlotData>[] {
    const spectraA = serviceResponses.modelA.spectra.find(
      spectra => spectra.sourceType === SourceType.TOTAL,
    );

    const {imts, percentDifference} = this.spectraPercentDifference(
      serviceResponses,
      form.returnPeriod,
    );

    const plotData = this.createSpectraPlotData({
      availableModels,
      form,
      name: () => '',
      serviceResponse: {
        ...serviceResponses.modelA,
        spectra: [
          {
            responseSpectra: {
              imts,
              responseSpectrum: [
                {
                  returnPeriod: form.returnPeriod,
                  values: percentDifference,
                },
              ],
              siteClass: spectraA.responseSpectra.siteClass,
            },
            sourceType: SourceType.TOTAL,
          },
        ],
      },
      spectraFilter: (spectra: Spectra) =>
        spectra.sourceType === SourceType.TOTAL,
    });

    plotData.forEach(data => (data.showlegend = false));
    plotData.forEach(
      data => (data.hovertemplate = '%{x} s, %{y} % difference'),
    );

    return plotData;
  }

  /**
   * Create the `PlotData` for a spectra plot.
   *
   * Creates plot data for response spectra for current return period.
   *
   * @param options The plot data options
   */
  private createSpectraPlotData(
    options: CreateSpectraPlotDataOptions,
  ): Partial<PlotData>[] {
    const modelInfo = this.mathService.getModelInfo(
      options.serviceResponse.model,
      options.availableModels,
    );

    const spectra = options.serviceResponse.spectra.filter(spectra =>
      options.spectraFilter(spectra),
    );
    const lines: Partial<PlotData>[] = [];
    let count = 0;
    const lineColors =
      options.colors === undefined ? plotUtils.COLORWAY : options.colors;

    spectra.map(spectrum => {
      const color =
        lineColors[count++ % Math.min(spectra.length, lineColors.length)];

      const returnPeriodSpectra =
        spectrum.responseSpectra.responseSpectrum.find(
          s => s.returnPeriod === options.form.returnPeriod,
        );

      const imts = spectrum.responseSpectra.imts;

      const spectraX: number[] = [];
      const spectraY: number[] = [];
      const scatters: Partial<PlotData>[] = [];
      const name = options.name(modelInfo, spectrum);

      for (
        let iSpectrum = 0;
        iSpectrum < returnPeriodSpectra.values.length;
        iSpectrum++
      ) {
        const imt = imts[iSpectrum];

        if (imt === Imt.PGV) {
          continue;
        } else if (imt === Imt.PGA) {
          scatters.push({
            hovertemplate: `${imt}, %{y} g`,
            legendgroup: imt,
            marker: {
              color,
              size: 7,
              symbol: options.pgaMarker ?? 'square',
            },
            mode: 'markers',
            name,
            showlegend: false,
            x: [imtToPeriod(imt)],
            y: [returnPeriodSpectra.values[iSpectrum]],
          });
        } else {
          spectraX.push(imtToPeriod(imt));
          spectraY.push(returnPeriodSpectra.values[iSpectrum]);
        }
      }

      lines.push({
        hovertemplate: '%{x} s, %{y} g',
        line: {
          color,
          dash: options.dash,
        },
        mode: 'lines+markers',
        name,
        x: spectraX,
        y: spectraY,
      });
      lines.push(...scatters);
    });

    return lines;
  }

  /**
   * Create a response spectra plot with given options.
   *
   * @param options The create plot options
   */
  private createSpectraPlot(options: CreateSpectraPlotOptions): PlotlyPlot {
    const {modelA, modelB} = options.serviceResponses;
    const lines: Partial<PlotData>[] = [];

    lines.push(
      ...this.createSpectraPlotData({
        ...options,
        colors: options.colors?.modelA,
        dash: options?.dash?.modelA,
        pgaMarker: options?.pgaMarker?.modelA,
        serviceResponse: modelA,
      }),
    );
    lines.push(
      ...this.createSpectraPlotData({
        ...options,
        colors: options.colors?.modelB,
        dash: options?.dash?.modelB,
        pgaMarker: options?.pgaMarker?.modelB,
        serviceResponse: modelB,
      }),
    );

    if (options?.pgaMarker?.modelA !== options?.pgaMarker?.modelB) {
      const modelAInfo = this.mathService.getModelInfo(
        options.serviceResponses.modelA.model,
        options.availableModels,
      );
      const modelBInfo = this.mathService.getModelInfo(
        options.serviceResponses.modelB.model,
        options.availableModels,
      );

      lines.push(
        this.spectraPgaLegendEntry(
          modelAInfo.display,
          options.pgaMarker.modelA,
        ),
      );
      lines.push(
        this.spectraPgaLegendEntry(
          modelBInfo.display,
          options.pgaMarker.modelB,
        ),
      );
    } else {
      lines.push(this.spectraPgaLegendEntry('PGA'));
    }

    return {
      config: {...options.plot.plotData.config},
      data: lines,
      id: options.id,
      layout: {...options.plot.plotData.layout},
      mobileConfig: {...options.plot.plotData.mobileConfig},
      mobileLayout: {...options.plot.plotData.mobileLayout},
    };
  }

  /**
   * Create the spectra PGA legend entry.
   *
   * @param name The legend entry name
   * @param pgaMarker The PGA marker symbol
   */
  private spectraPgaLegendEntry(name: string, pgaMarker = 'square') {
    const legendEntry: Partial<PlotData> = {
      legendgroup: Imt.PGA,
      marker: {
        color: 'white',
        line: {
          width: 1.5,
        },
        size: 7,
        symbol: pgaMarker,
      },
      mode: 'markers',
      name,
      x: [-1],
      y: [-1],
    };

    return legendEntry;
  }
}

results matching ""

    No results matching ""