Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion frontend/src/app/models/compared-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as _ from 'lodash';
import { Store } from '../store/types';
import { Observable } from 'rxjs';
import { queryFromParams, queryToParams } from '../utils/params';
import { filter, map, takeUntil } from 'rxjs/operators';
import { distinctUntilChanged, distinctUntilKeyChanged, filter, map, takeUntil } from 'rxjs/operators';

interface CompareQueryState {
primary: string|undefined;
Expand All @@ -18,6 +18,9 @@ export class ComparedQueries extends StoreSync<CompareQueryState> {
/** all queries, including the primary query text */
allQueries$: Observable<string[]>;

/** compared queries, excluding the primary query text */
comparedQueries$: Observable<string[]>;

protected keysInStore = ['query', COMPARE_TERMS_KEY];

constructor(store: Store) {
Expand All @@ -29,6 +32,10 @@ export class ComparedQueries extends StoreSync<CompareQueryState> {
this.allQueries$ = this.state$.pipe(
map(state => [state.primary, ...state.compare])
);
this.comparedQueries$ = this.state$.pipe(
distinctUntilKeyChanged('compare', _.isEqual),
map(state => state.compare),
);
}

reset() {
Expand Down
71 changes: 71 additions & 0 deletions frontend/src/app/visualization/barchart/barchart-data-histogram.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { TermsAggregator, TermsResult } from '@models/aggregation';
import { BarchartData } from './barchart-data';
import { HistogramDataPoint, HistogramSeries, MultipleChoiceFilterOptions, QueryModel, RangeFilterOptions, TaskResult } from '@models';
import _ from 'lodash';

export class HistogramData extends BarchartData<TermsResult, HistogramDataPoint> {
fullDataRequest(): Promise<TaskResult> {
const paramsPerSeries = this.rawData$.value.map((series) => {
const queryModel = this.queryModelForSeries(series, this.queryModel);
const bins = this.makeTermFrequencyBins(series);
return this.visualizationService.makeAggregateTermFrequencyParameters(
this.queryModel.corpus,
queryModel,
this.visualizedField.name,
bins
);
});
return this.apiService.requestFullData({
visualization: 'aggregate_term_frequency',
parameters: paramsPerSeries,
corpus_name: this.queryModel.corpus.name,
});
}

/** specify aggregator object based on visualised field;
* used in document requests.
*/
protected getAggregator(): TermsAggregator {
let size = 100;

const filterOptions = this.visualizedField.filterOptions;
if (filterOptions?.name === 'MultipleChoiceFilter') {
size = (filterOptions as MultipleChoiceFilterOptions).option_count;
} else if (filterOptions?.name === 'RangeFilter') {
const filterRange =
(filterOptions as RangeFilterOptions).upper -
(filterOptions as RangeFilterOptions).lower;
size = _.max([size, filterRange])
}
return new TermsAggregator(this.visualizedField, size);
}

protected requestSeriesDocCounts(queryModel: QueryModel) {
const aggregator = this.getAggregator();
return this.searchService.aggregateSearch(this.queryModel.corpus, queryModel, aggregator);
}

protected aggregateResultToDataPoint(cat: TermsResult) {
return cat;
}

protected requestSeriesTermFrequency(
series: HistogramSeries,
queryModel: QueryModel
) {
const bins = this.makeTermFrequencyBins(series);
return this.visualizationService.aggregateTermFrequencySearch(
this.queryModel.corpus,
queryModel,
this.visualizedField.name,
bins
);
}

private makeTermFrequencyBins(series: HistogramSeries) {
return series.data.map((bin) => ({
fieldValue: bin.key,
size: this.documentLimitForCategory(bin, series),
}));
}
}
151 changes: 151 additions & 0 deletions frontend/src/app/visualization/barchart/barchart-data-timeline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { BarchartData } from './barchart-data';
import { BarchartSeries, DateFilterData, QueryModel, TimeCategory, TimelineDataPoint, TimelineSeries } from '@models';
import { DateHistogramAggregator, DateHistogramResult } from '@models/aggregation';
import { differenceInDays } from 'date-fns';

export class TimelineData extends BarchartData<DateHistogramResult, TimelineDataPoint> {
xDomain: [Date, Date];
timeCategory: TimeCategory = 'year';

/** threshold for scaling down a unit on the time scale */
private scaleDownThreshold = 10;

zoomedInData (min: Date, max: Date): Promise<BarchartSeries<TimelineDataPoint>[]> {
const promises = this.rawData$.value.map(series => {
const queryModelCopy = this.addQueryDateFilter(this.queryModel, min, max);
return this.getSeriesDocumentData(
series, queryModelCopy, false
).then((result) => {
if (this.frequencyMeasure === 'tokens') {
return this.getTermFrequencies(result, queryModelCopy);
} else {
return result;
}
});
});
return Promise.all(promises);
}

fullDataRequest() {
const paramsPerSeries = this.rawData$.value.map((series) => {
const queryModel = this.queryModelForSeries(series, this.queryModel);
const bins = this.makeTermFrequencyBins(series);
const unit = this.timeCategory;
return this.visualizationService.makeDateTermFrequencyParameters(
this.queryModel.corpus,
queryModel,
this.visualizedField.name,
bins,
unit
);
});
return this.apiService.requestFullData({
visualization: 'date_term_frequency',
parameters: paramsPerSeries,
corpus_name: this.queryModel.corpus.name,
});
}

/**
* Get the time category (year/month/week/day) that should be used in the graph,
* based on minimum and maximum dates on the x axis.
*/
calculateTimeCategory(min: Date, max: Date): TimeCategory {
const diff = differenceInDays(max, min);
if (diff <= this.scaleDownThreshold) {
return 'day';
} else if (diff <= this.scaleDownThreshold * 7) {
return 'week';
} else if (diff <= this.scaleDownThreshold * 30) {
return 'month';
} else {
return 'year';
}
}

protected refresh(): void {
this.setTimeDomain();
super.refresh();
}

protected aggregateResultToDataPoint(cat: DateHistogramResult): TimelineDataPoint {
/* date fields are returned with keys containing identifiers by elasticsearch
replace with string representation, contained in 'key_as_string' field
*/
return {
date: new Date(cat.key_as_string),
doc_count: cat.doc_count,
};
}

/** Retrieve doc counts for a series.
*
* @param series series object
* @param setSearchRatio whether the `searchRatio` property of the series should be updated.
* True when retrieving results for the entire series, false when retrieving a window.
*/
protected requestSeriesDocCounts(queryModel: QueryModel) {
const aggregation = new DateHistogramAggregator(
this.visualizedField,
this.timeCategory
);
return this.searchService.aggregateSearch(this.queryModel.corpus, queryModel, aggregation);
}

protected requestSeriesTermFrequency(series: TimelineSeries, queryModel: QueryModel) {
const bins = this.makeTermFrequencyBins(series);
return this.visualizationService.dateTermFrequencySearch(
this.queryModel.corpus,
queryModel,
this.visualizedField.name,
bins,
this.timeCategory
);
}

/**
* Add a date filter to a query model restricting it to the provided min and max values.
*/
private addQueryDateFilter(query: QueryModel, min: Date, max: Date): QueryModel {
const queryModelCopy = query.clone();
// download zoomed in results
const filter = this.visualizedField.makeSearchFilter();
filter.set({ min, max });
queryModelCopy.addFilter(filter);
return queryModelCopy;
}

private makeTermFrequencyBins(series: TimelineSeries) {
return series.data.map((bin, index) => {
const [minDate, maxDate] = this.categoryTimeDomain(
bin, index, series
);
return {
start_date: minDate,
end_date: maxDate,
size: this.documentLimitForCategory(bin, series),
};
});
}

/** get min/max date for the entire graph and set domain and time category */
private setTimeDomain() {
const filter = this.queryModel.filterForField(this.visualizedField)
|| this.visualizedField.makeSearchFilter();
const currentDomain = filter.currentData as DateFilterData;
const min = new Date(currentDomain.min);
const max = new Date(currentDomain.max);
this.xDomain = [min, max];
this.timeCategory = this.calculateTimeCategory(min, max);
}

/** time domain for a bin */
private categoryTimeDomain(cat, catIndex, series): [Date, Date] {
const startDate = cat.date;
const endDate =
catIndex < series.data.length - 1
? series.data[catIndex + 1].date
: undefined;
return [startDate, endDate];
}
}
Loading