Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LLCAXCHZF-56 #3

Merged
merged 3 commits into from
Jul 29, 2024
Merged
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
2 changes: 1 addition & 1 deletion ckanext/charts/assets/css/charts.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

111 changes: 111 additions & 0 deletions ckanext/charts/assets/js/vendor/chartjs-adapter-moment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*

Purpose:
To handle and display date and time data in Chart.js scatter and bubble charts with type: 'time' on the x-axis.

Why We Use It:
1. Date Parsing and Formatting:
- Enables robust parsing and formatting of date strings using the moment library.

2. Chart.js Compatibility:
- Integrates with Chart.js for time-based data handling.
- Necessary for using type: 'time' in chart configurations.

3. Advanced Date Manipulation:
- Provides functions for date manipulation like adding/subtracting time and calculating date differences.

Example Usage:
To use type: 'time' for the x-axis in scatter and bubble charts:

const config = {
type: 'scatter',
data: {...},
options: {
scales: {
x: {
type: 'time',
time: {
unit: 'day'
}
}
}
}
};

The moment adapter ensures accurate processing and display of date and time data in Chart.js.

*/


/*!
* chartjs-adapter-moment v1.0.0
* https://www.chartjs.org
* (c) 2021 chartjs-adapter-moment Contributors
* Released under the MIT license
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('moment'), require('chart.js')) :
typeof define === 'function' && define.amd ? define(['moment', 'chart.js'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.moment, global.Chart));
}(this, (function (moment, chart_js) { 'use strict';

function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

var moment__default = /*#__PURE__*/_interopDefaultLegacy(moment);

const FORMATS = {
datetime: 'MMM D, YYYY, h:mm:ss a',
millisecond: 'h:mm:ss.SSS a',
second: 'h:mm:ss a',
minute: 'h:mm a',
hour: 'hA',
day: 'MMM D',
week: 'll',
month: 'MMM YYYY',
quarter: '[Q]Q - YYYY',
year: 'YYYY'
};

chart_js._adapters._date.override(typeof moment__default['default'] === 'function' ? {
_id: 'moment', // DEBUG ONLY

formats: function() {
return FORMATS;
},

parse: function(value, format) {
if (typeof value === 'string' && typeof format === 'string') {
value = moment__default['default'](value, format);
} else if (!(value instanceof moment__default['default'])) {
value = moment__default['default'](value);
}
return value.isValid() ? value.valueOf() : null;
},

format: function(time, format) {
return moment__default['default'](time).format(format);
},

add: function(time, amount, unit) {
return moment__default['default'](time).add(amount, unit).valueOf();
},

diff: function(max, min, unit) {
return moment__default['default'](max).diff(moment__default['default'](min), unit);
},

startOf: function(time, unit, weekday) {
time = moment__default['default'](time);
if (unit === 'isoWeek') {
weekday = Math.trunc(Math.min(Math.max(0, weekday), 6));
return time.isoWeekday(weekday).startOf('day').valueOf();
}
return time.startOf(unit).valueOf();
},

endOf: function(time, unit) {
return moment__default['default'](time).endOf(unit).valueOf();
}
} : {});

})));
1 change: 1 addition & 0 deletions ckanext/charts/assets/webassets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ chartjs:
output: ckanext-charts/%(version)s-chartjs.js
contents:
- js/vendor/chartjs.min.js
- js/vendor/chartjs-adapter-moment.js
- js/charts-render-chartjs.js
extra:
preload:
Expand Down
12 changes: 12 additions & 0 deletions ckanext/charts/chart_builders/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,18 @@ def height_field(self) -> dict[str, Any]:
"group": "Data",
}

def more_info_button_field(self) -> dict[str, Any]:
"""
Adds a "More info" button to the Data tab in the form, which triggers a pop-up.
This pop-up provides users with information about supported date formats.
"""
return {
"field_name": "more_info",
"label": "More info",
"form_snippet": "chart_more_info_button.html",
"group": "Data",
}

def size_field(self, choices: list[dict[str, str]]) -> dict[str, Any]:
field = self.column_field(choices)
field.update({"field_name": "size", "label": "Size", "group": "Structure"})
Expand Down
34 changes: 31 additions & 3 deletions ckanext/charts/chart_builders/chartjs.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_multi_axis_field(columns),
self.more_info_button_field(),
self.limit_field(),
self.filter_field(columns),
]
Expand Down Expand Up @@ -155,6 +156,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_multi_axis_field(columns),
self.more_info_button_field(),
self.sort_x_field(),
self.sort_y_field(),
self.limit_field(),
Expand Down Expand Up @@ -213,6 +215,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.values_field(columns),
self.names_field(columns),
self.more_info_button_field(),
self.limit_field(),
self.filter_field(columns),
]
Expand Down Expand Up @@ -254,8 +257,32 @@ def to_json(self) -> str:
"data": dataset_data,
}
]
return json.dumps(self._configure_date_axis(data))

def _configure_date_axis(self, data: dict[str, Any]) -> dict[str, Any]:
"""
Configure date settings for the x-axis if it uses 'date_time'.
"""
x_axis = data["options"]["x"]
scales = data["options"].get("scales", {})

if x_axis == "date_time":
x_scale = scales.get("x", {})
x_scale.update(
{
"type": "time",
"time": {
"unit": "day",
"displayFormats": {"day": "YYYY-MM-DD"},
},
}
)
scales["x"] = x_scale

return json.dumps(data)
if scales:
data["options"]["scales"] = scales

return data


class ChartJSScatterForm(BaseChartForm):
Expand All @@ -277,6 +304,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_axis_field(columns),
self.more_info_button_field(),
self.sort_x_field(),
self.sort_y_field(),
self.limit_field(),
Expand Down Expand Up @@ -312,8 +340,7 @@ def to_json(self) -> str:
data["data"]["datasets"] = [
{"label": self.settings["y"], "data": dataset_data},
]

return json.dumps(data)
return json.dumps(self._configure_date_axis(data))

def _calculate_bubble_radius(self, data_series: pd.Series, max_size: int) -> int:
"""Calculate bubble radius based on the size column"""
Expand Down Expand Up @@ -403,6 +430,7 @@ def get_form_fields(self):
columns,
help_text="Select 3 or more different categorical variables (dimensions)",
),
self.more_info_button_field(),
self.limit_field(),
self.filter_field(columns),
]
5 changes: 5 additions & 0 deletions ckanext/charts/chart_builders/observable.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_axis_field(columns),
self.more_info_button_field(),
self.sort_x_field(),
self.sort_y_field(),
self.fill_field(columns),
Expand Down Expand Up @@ -112,6 +113,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_axis_field(columns),
self.more_info_button_field(),
self.invert_x_field(),
self.invert_y_field(),
self.sort_x_field(),
Expand Down Expand Up @@ -187,6 +189,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.values_field(columns),
self.names_field(columns),
self.more_info_button_field(),
self.opacity_field(),
self.inner_radius_field(),
self.stroke_width_field(),
Expand Down Expand Up @@ -227,6 +230,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_axis_field(columns),
self.more_info_button_field(),
self.sort_x_field(),
self.sort_y_field(),
self.color_field(columns),
Expand Down Expand Up @@ -265,6 +269,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_axis_field(columns),
self.more_info_button_field(),
self.sort_x_field(),
self.sort_y_field(),
self.color_field(columns),
Expand Down
4 changes: 4 additions & 0 deletions ckanext/charts/chart_builders/plotly.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_axis_field(columns),
self.more_info_button_field(),
self.log_x_field(),
self.log_y_field(),
self.sort_x_field(),
Expand Down Expand Up @@ -143,6 +144,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.values_field(columns),
self.names_field(columns),
self.more_info_button_field(),
self.opacity_field(),
self.limit_field(),
self.filter_field(columns),
Expand Down Expand Up @@ -185,6 +187,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.plotly_y_multi_axis_field(columns, 2),
self.more_info_button_field(),
self.invert_x_field(),
self.invert_y_field(),
self.sort_x_field(),
Expand Down Expand Up @@ -229,6 +232,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_axis_field(columns),
self.more_info_button_field(),
self.log_x_field(),
self.log_y_field(),
self.sort_x_field(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<div class="form-group control-full control-large">
TomeCirun marked this conversation as resolved.
Show resolved Hide resolved
<div class="controls">
<button class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#moreInfoModal">
{{ _('More info') }}
</button>
</div>
</div>

{% set datetime_mapping = {
"YYYY-MM-DD": "2023-07-24",
"YYYY/MM/DD": "2023/07/24",
"MM-DD-YYYY": "07-24-2023",
"MM/DD/YYYY": "07/24/2023",
"DD-MM-YYYY": "24-07-2023",
"DD/MM/YYYY": "24/07/2023",
"dd/MMM/yyyy": "25/Nov/2023",
"YYYYMMDD": "20231125",
"YYYY-MM-DDTHH:MM:SS": "2023-07-24T14:30:00",
"YYYY-MM-DD HH:mm:ss": "2023-11-25 12:34:56",
"YYYY-MM-DDTHH:mm:ssZ": "2023-11-25T12:34:56Z",
"YYYY-MM-DD HH:mm:ss.SSS": "2023-11-25 12:34:56.789",
"MM/dd/yyyy hh:mm:ss a": "9/28/2023 2:23:15 PM",
"MM/dd/yyyy hh:mm:ss a:SSS": "8/5/2023 3:31:18 AM:234",
"MMdd_HH:mm:ss.SSS": "0423_11:42:35.883",
"dd MMM yyyy HH:mm:ss*SSS": "23 Apr 2023 10:32:35*311",
"dd MMM yyyy HH:mm:ss": "23 Apr 2023 11:42:35",
"yyMMdd HH:mm:ss": "220423 11:42:35",
"yy-MM-dd HH:mm:ss": "23-04-19 12:00:17"
} %}

<div class="modal fade" id="moreInfoModal" tabindex="-1" role="dialog" aria-labelledby="moreInfoModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="moreInfoModalLabel">{{ _("Supported Date Formats") }}</h5>
<button type="button" class="btn-close close" data-bs-dismiss="modal" aria-label="Close">
</button>
</div>
<div class="modal-body">
<div class="date-formats-container">
<table class="table">
<thead>
<tr>
<th>{{ _("Timestamp Format") }}</th>
<th>{{ _("Example") }}</th>
</tr>
</thead>

{% for format, example in datetime_mapping.items() %}
<tr>
<td>{{ format }}</td>
<td>{{ example }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">{{ _("Close") }}</button>
</div>
</div>
</div>
</div>
5 changes: 5 additions & 0 deletions ckanext/charts/theme/mixins.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.modal-open {
.module-resource {
z-index: unset;
}
}
Loading